徐福静0235668 2 dni temu
rodzic
commit
073fb25f3b
25 zmienionych plików z 4121 dodań i 1751 usunięć
  1. 13 0
      public/assets/images/default-avatar.svg
  2. 10 0
      public/assets/images/hr-icon.svg
  3. 11 0
      public/assets/images/hr-logo.svg
  4. 14 0
      public/assets/images/portfolio-1.svg
  5. 14 0
      public/assets/images/portfolio-2.svg
  6. 14 0
      public/assets/images/portfolio-3.svg
  7. 14 0
      public/assets/images/portfolio-4.svg
  8. 8 2
      src/app/app.routes.ts
  9. 9 7
      src/app/pages/customer-service/consultation-order/consultation-order.html
  10. 92 9
      src/app/pages/customer-service/consultation-order/consultation-order.scss
  11. 14 3
      src/app/pages/customer-service/consultation-order/consultation-order.ts
  12. 5 5
      src/app/pages/customer-service/customer-service-layout/customer-service-layout.html
  13. 2 2
      src/app/pages/customer-service/customer-service-layout/customer-service-layout.ts
  14. 221 0
      src/app/pages/hr/dashboard/dashboard.html
  15. 704 0
      src/app/pages/hr/dashboard/dashboard.scss
  16. 381 0
      src/app/pages/hr/dashboard/dashboard.ts
  17. 370 300
      src/app/pages/hr/designer-profile/designer-profile.html
  18. 420 995
      src/app/pages/hr/designer-profile/designer-profile.scss
  19. 326 423
      src/app/pages/hr/designer-profile/designer-profile.ts
  20. 229 1
      src/app/pages/hr/employee-records/employee-records.html
  21. 247 0
      src/app/pages/hr/employee-records/employee-records.scss
  22. 798 4
      src/app/pages/hr/employee-records/employee-records.ts
  23. 60 0
      src/app/pages/hr/hr-layout/hr-layout.html
  24. 112 0
      src/app/pages/hr/hr-layout/hr-layout.scss
  25. 33 0
      src/app/pages/hr/hr-layout/hr-layout.ts

+ 13 - 0
public/assets/images/default-avatar.svg

@@ -0,0 +1,13 @@
+<svg width="160" height="160" viewBox="0 0 160 160" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="默认头像">
+  <defs>
+    <linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
+      <stop offset="0%" stop-color="#165DFF"/>
+      <stop offset="100%" stop-color="#7c3aed"/>
+    </linearGradient>
+  </defs>
+  <rect width="160" height="160" rx="24" fill="url(#g)"/>
+  <g transform="translate(80,78)" fill="#fff">
+    <circle cx="0" cy="-28" r="22" opacity="0.95"/>
+    <path d="M-46,40 a46,46 0 1,1 92,0 v8 h-92 z" opacity="0.95"/>
+  </g>
+</svg>

+ 10 - 0
public/assets/images/hr-icon.svg

@@ -0,0 +1,10 @@
+<svg width="64" height="64" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="HR Icon">
+  <defs>
+    <linearGradient id="ig" x1="0" y1="0" x2="1" y2="1">
+      <stop offset="0%" stop-color="#165DFF"/>
+      <stop offset="100%" stop-color="#7c3aed"/>
+    </linearGradient>
+  </defs>
+  <rect width="64" height="64" rx="14" fill="url(#ig)"/>
+  <text x="32" y="40" font-size="28" font-family="Segoe UI, Roboto, Arial" text-anchor="middle" fill="#fff" font-weight="700">HR</text>
+</svg>

+ 11 - 0
public/assets/images/hr-logo.svg

@@ -0,0 +1,11 @@
+<svg width="200" height="60" viewBox="0 0 200 60" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="HR Logo">
+  <defs>
+    <linearGradient id="lg" x1="0" y1="0" x2="1" y2="1">
+      <stop offset="0%" stop-color="#165DFF"/>
+      <stop offset="100%" stop-color="#7c3aed"/>
+    </linearGradient>
+  </defs>
+  <rect width="200" height="60" rx="12" fill="#fff"/>
+  <rect x="6" y="6" width="188" height="48" rx="10" fill="url(#lg)"/>
+  <text x="100" y="38" font-size="28" font-family="Segoe UI, Roboto, Arial" text-anchor="middle" fill="#fff" font-weight="700">HR Center</text>
+</svg>

+ 14 - 0
public/assets/images/portfolio-1.svg

@@ -0,0 +1,14 @@
+<svg width="320" height="200" viewBox="0 0 320 200" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="作品集占位1">
+  <defs>
+    <linearGradient id="p1" x1="0" y1="0" x2="1" y2="1">
+      <stop offset="0%" stop-color="#e6f7ff"/>
+      <stop offset="100%" stop-color="#f3e8ff"/>
+    </linearGradient>
+  </defs>
+  <rect width="320" height="200" rx="12" fill="url(#p1)"/>
+  <g fill="#1a3a6e" opacity="0.8">
+    <rect x="28" y="48" width="120" height="16" rx="8"/>
+    <rect x="28" y="72" width="180" height="12" rx="6"/>
+    <rect x="28" y="92" width="140" height="12" rx="6"/>
+  </g>
+</svg>

+ 14 - 0
public/assets/images/portfolio-2.svg

@@ -0,0 +1,14 @@
+<svg width="320" height="200" viewBox="0 0 320 200" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="作品集占位2">
+  <defs>
+    <linearGradient id="p2" x1="0" y1="0" x2="1" y2="1">
+      <stop offset="0%" stop-color="#fff7e6"/>
+      <stop offset="100%" stop-color="#e6fffb"/>
+    </linearGradient>
+  </defs>
+  <rect width="320" height="200" rx="12" fill="url(#p2)"/>
+  <g fill="#1a3a6e" opacity="0.8">
+    <rect x="28" y="48" width="120" height="16" rx="8"/>
+    <rect x="28" y="72" width="180" height="12" rx="6"/>
+    <rect x="28" y="92" width="140" height="12" rx="6"/>
+  </g>
+</svg>

+ 14 - 0
public/assets/images/portfolio-3.svg

@@ -0,0 +1,14 @@
+<svg width="320" height="200" viewBox="0 0 320 200" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="作品集占位3">
+  <defs>
+    <linearGradient id="p3" x1="0" y1="0" x2="1" y2="1">
+      <stop offset="0%" stop-color="#f0f5ff"/>
+      <stop offset="100%" stop-color="#f9f0ff"/>
+    </linearGradient>
+  </defs>
+  <rect width="320" height="200" rx="12" fill="url(#p3)"/>
+  <g fill="#1a3a6e" opacity="0.8">
+    <rect x="28" y="48" width="120" height="16" rx="8"/>
+    <rect x="28" y="72" width="180" height="12" rx="6"/>
+    <rect x="28" y="92" width="140" height="12" rx="6"/>
+  </g>
+</svg>

+ 14 - 0
public/assets/images/portfolio-4.svg

@@ -0,0 +1,14 @@
+<svg width="320" height="200" viewBox="0 0 320 200" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="作品集占位4">
+  <defs>
+    <linearGradient id="p4" x1="0" y1="0" x2="1" y2="1">
+      <stop offset="0%" stop-color="#e6fffb"/>
+      <stop offset="100%" stop-color="#fff0f6"/>
+    </linearGradient>
+  </defs>
+  <rect width="320" height="200" rx="12" fill="url(#p4)"/>
+  <g fill="#1a3a6e" opacity="0.8">
+    <rect x="28" y="48" width="120" height="16" rx="8"/>
+    <rect x="28" y="72" width="180" height="12" rx="6"/>
+    <rect x="28" y="92" width="140" height="12" rx="6"/>
+  </g>
+</svg>

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

@@ -33,9 +33,12 @@ import { Reconciliation } from './pages/finance/reconciliation/reconciliation';
 import { Reports } from './pages/finance/reports/reports';
 
 // 人事/行政页面
+import { HrLayout } from './pages/hr/hr-layout/hr-layout';
+import { Dashboard as HrDashboard } from './pages/hr/dashboard/dashboard';
 import { EmployeeRecords } from './pages/hr/employee-records/employee-records';
 import { Attendance } from './pages/hr/attendance/attendance';
 import { Assets } from './pages/hr/assets/assets';
+import { DesignerProfile } from './pages/hr/designer-profile/designer-profile';
 
 // 管理员页面
 import { AdminLayout } from './pages/admin/admin-layout/admin-layout';
@@ -110,11 +113,14 @@ export const routes: Routes = [
   // 人事/行政路由
   {
     path: 'hr',
+    component: HrLayout,
     children: [
-      { path: '', redirectTo: 'employee-records', pathMatch: 'full' },
+      { path: '', redirectTo: 'dashboard', pathMatch: 'full' },
+      { path: 'dashboard', component: HrDashboard, title: '人事看板' },
       { path: 'employee-records', component: EmployeeRecords, title: '花名册与档案库' },
       { path: 'attendance', component: Attendance, title: '考勤统计' },
-      { path: 'assets', component: Assets, title: '资产管理' }
+      { path: 'assets', component: Assets, title: '资产管理' },
+      { path: 'designer-profile/:id', component: DesignerProfile, title: '设计师详情' }
     ]
   },
 

+ 9 - 7
src/app/pages/customer-service/consultation-order/consultation-order.html

@@ -20,7 +20,7 @@
           <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
           <polyline points="22 4 12 14.01 9 11.01"></polyline>
         </svg>
-        <span>表单提交成功!</span>
+        <span>表单提交成功!项目已创建</span>
       </div>
     </div>
   </header>
@@ -65,8 +65,8 @@
               </svg>
               <input 
                 type="text" 
-                [(ngModel)]="searchKeyword" 
-                (input)="searchCustomer()"
+                [ngModel]="searchKeyword()" 
+                (ngModelChange)="searchKeyword.set($event); searchCustomer()"
                 placeholder="搜索客户姓名或手机号..."
                 class="search-input"
                 autocomplete="off"
@@ -165,15 +165,17 @@
             <div class="tags-section">
               <div class="current-tags">
                 <mat-chip-grid #chipList aria-label="偏好标签">
-                  <mat-chip
+                  <mat-chip-row
                     *ngFor="let tag of preferenceTags"
-                    removable
+                    [removable]="true"
                     (removed)="removePreferenceTag(tag)"
                     class="preference-chip"
                   >
                     {{ tag }}
-                    <mat-icon matChipRemove>cancel</mat-icon>
-                  </mat-chip>
+                    <button matChipRemove aria-label="移除 {{tag}}">
+                      <mat-icon>cancel</mat-icon>
+                    </button>
+                  </mat-chip-row>
                   <input
                     placeholder="添加自定义标签..."
                     [matChipInputFor]="chipList"

+ 92 - 9
src/app/pages/customer-service/consultation-order/consultation-order.scss

@@ -27,6 +27,7 @@ $card-padding: 16px;
   background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
   min-height: 100vh;
   padding: 0;
+  animation: fadeIn 0.5s ease-in-out;
 }
 
 .main-content {
@@ -89,20 +90,39 @@ $card-padding: 16px;
 }
 
 // 成功提示样式
-.success-message {
+.success-toast {
+  position: fixed;
+  top: 20px;
+  left: 50%;
+  transform: translateX(-50%);
   background: linear-gradient(135deg, #10b981 0%, #059669 100%);
   color: white;
-  padding: 20px 24px;
-  border-radius: 16px;
+  padding: 12px 24px;
+  border-radius: 50px;
   box-shadow: 0 8px 32px rgba(16, 185, 129, 0.3);
   text-align: center;
   font-weight: 500;
-  animation: slideInFromTop 0.5s ease-out;
+  animation: slideIn 0.5s ease-out;
   border: 1px solid rgba(255, 255, 255, 0.2);
+  z-index: 1000;
   
-  .success-icon {
-    margin-right: 8px;
-    vertical-align: middle;
+  .toast-content {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+  }
+  
+  svg {
+    flex-shrink: 0;
+  }
+}
+
+@keyframes fadeIn {
+  from {
+    opacity: 0;
+  }
+  to {
+    opacity: 1;
   }
 }
 
@@ -117,6 +137,17 @@ $card-padding: 16px;
   }
 }
 
+@keyframes slideInUp {
+  from {
+    opacity: 0;
+    transform: translateY(20px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
 // 现代化信息卡片样式
 .info-card {
   background: white;
@@ -125,11 +156,25 @@ $card-padding: 16px;
   border: 1px solid rgba(0, 0, 0, 0.05);
   overflow: hidden;
   transition: all 0.3s ease;
+  animation: slideInUp 0.5s ease-out;
+  animation-fill-mode: both;
   
   &:hover {
     transform: translateY(-2px);
     box-shadow: 0 12px 48px rgba(0, 0, 0, 0.12);
   }
+  
+  &:nth-child(1) {
+    animation-delay: 0.1s;
+  }
+  
+  &:nth-child(2) {
+    animation-delay: 0.2s;
+  }
+  
+  &:nth-child(3) {
+    animation-delay: 0.3s;
+  }
 }
 
 // 卡片头部样式
@@ -246,6 +291,7 @@ $card-padding: 16px;
 .form-field {
   display: flex;
   flex-direction: column;
+  transition: all 0.3s ease;
   
   &.full-width {
     grid-column: 1 / -1;
@@ -256,6 +302,7 @@ $card-padding: 16px;
     font-weight: 600;
     color: #374151;
     margin-bottom: 8px;
+    transition: color 0.2s ease;
     
     .required {
       color: #ef4444;
@@ -270,9 +317,13 @@ $card-padding: 16px;
     border: 2px solid #e5e7eb;
     border-radius: 12px;
     font-size: 0.875rem;
-    transition: all 0.2s ease;
+    transition: all 0.3s ease;
     background: white;
     
+    &:hover {
+      border-color: #d1d5db;
+    }
+    
     &:focus {
       outline: none;
       border-color: #3b82f6;
@@ -281,6 +332,11 @@ $card-padding: 16px;
     
     &::placeholder {
       color: #9ca3af;
+      transition: opacity 0.2s ease;
+    }
+    
+    &:focus::placeholder {
+      opacity: 0.7;
     }
   }
   
@@ -1034,9 +1090,36 @@ $card-padding: 16px;
   }
 }
 
-// 操作按钮区域样式
+// 操作按钮区域
 .action-section {
   margin-top: 40px;
+  
+  .action-buttons {
+    display: flex;
+    gap: 16px;
+    justify-content: center;
+    
+    button {
+      transition: all 0.3s ease;
+      
+      &:hover:not([disabled]) {
+        transform: translateY(-2px);
+        box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
+      }
+      
+      &:active:not([disabled]) {
+        transform: translateY(0);
+      }
+    }
+    
+    .btn-primary {
+      background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
+    }
+    
+    .btn-secondary {
+      background: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%);
+    }
+  }
   padding-top: 32px;
   border-top: 1px solid #e2e8f0;
   

+ 14 - 3
src/app/pages/customer-service/consultation-order/consultation-order.ts

@@ -7,8 +7,9 @@ import { MatChipInputEvent } from '@angular/material/chips';
 import { COMMA, ENTER } from '@angular/cdk/keycodes';
 import { MatChipsModule } from '@angular/material/chips';
 import { MatIconModule } from '@angular/material/icon';
-import { MatSnackBar } from '@angular/material/snack-bar';
-import { MatDialog, MAT_DIALOG_DATA } from '@angular/material/dialog';
+import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
+import { MatDialog, MatDialogModule, MAT_DIALOG_DATA } from '@angular/material/dialog';
+import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
 import { ProjectGroupDialog } from './project-group-dialog.component';
 
 // 定义客户信息接口
@@ -68,7 +69,17 @@ const PREFERENCE_TAG_OPTIONS = [
 @Component({
   selector: 'app-consultation-order',
   standalone: true,
-  imports: [CommonModule, FormsModule, ReactiveFormsModule, RouterModule, MatChipsModule, MatIconModule],
+  imports: [
+    CommonModule, 
+    FormsModule, 
+    ReactiveFormsModule, 
+    RouterModule, 
+    MatChipsModule, 
+    MatIconModule,
+    MatSnackBarModule,
+    MatDialogModule,
+    MatProgressSpinnerModule
+  ],
   templateUrl: './consultation-order.html',
   styleUrls: ['./consultation-order.scss', '../customer-service-styles.scss']
 })

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

@@ -78,19 +78,19 @@
   <!-- 左侧侧边栏 -->
   <aside class="sidebar" [class.collapsed]="!sidebarOpen">
     <nav class="sidebar-nav">
-      <a href="/customer-service/dashboard" class="nav-item" routerLinkActive="active">
+      <a routerLink="/customer-service/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="/customer-service/consultation-order" class="nav-item" routerLinkActive="active">
+      <a routerLink="/customer-service/consultation-order" class="nav-item" routerLinkActive="active">
         <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
           <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
         </svg>
         <span>客户咨询</span>
       </a>
-      <a href="/customer-service/project-list" class="nav-item" routerLinkActive="active">
+      <a routerLink="/customer-service/project-list" class="nav-item" routerLinkActive="active">
         <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
           <line x1="8" y1="6" x2="21" y2="6"></line>
           <line x1="8" y1="12" x2="21" y2="12"></line>
@@ -101,7 +101,7 @@
         </svg>
         <span>项目列表</span>
       </a>
-      <a href="/customer-service/project-detail/1" class="nav-item" routerLinkActive="active">
+      <a routerLink="/customer-service/project-detail/1" class="nav-item" routerLinkActive="active">
         <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
           <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
           <polyline points="14 2 14 8 20 8"></polyline>
@@ -111,7 +111,7 @@
         </svg>
         <span>项目详情</span>
       </a>
-      <a href="/customer-service/case-library" class="nav-item" routerLinkActive="active">
+      <a routerLink="/customer-service/case-library" class="nav-item" routerLinkActive="active">
         <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
           <path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path>
           <path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path>

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

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

+ 221 - 0
src/app/pages/hr/dashboard/dashboard.html

@@ -0,0 +1,221 @@
+<div class="dashboard-container">
+  <header class="page-header">
+    <h1>人事看板</h1>
+    <p class="page-description">人事数据总览,聚焦入职/离职动态、绩效核心指标、风险预警三大维度</p>
+  </header>
+
+  <!-- 时间维度筛选 -->
+  <div class="filter-bar">
+    <div class="time-filter">
+      <span class="filter-label">时间维度:</span>
+      <div class="filter-buttons">
+        <button mat-button [class.active]="timeFilter() === 'day'" (click)="changeTimeFilter('day')">日</button>
+        <button mat-button [class.active]="timeFilter() === 'week'" (click)="changeTimeFilter('week')">周</button>
+        <button mat-button [class.active]="timeFilter() === 'month'" (click)="changeTimeFilter('month')">月</button>
+      </div>
+    </div>
+  </div>
+
+  <!-- 主内容区 -->
+  <div class="dashboard-content">
+    <!-- 入职离职看板 -->
+    <mat-card class="dashboard-card">
+      <mat-card-header>
+        <mat-card-title>
+          <div class="card-title-container">
+            <mat-icon>trending_up</mat-icon>
+            <span>入职离职看板</span>
+          </div>
+        </mat-card-title>
+      </mat-card-header>
+      <mat-card-content>
+        <div class="card-section">
+          <h3>月度入职离职数据</h3>
+          <!-- 用 ECharts 展示分组柱状图(入职/离职) -->
+          <div id="hrMovementChart" class="chart-container"></div>
+        </div>
+
+        <div class="card-section">
+          <h3>流程进度追踪</h3>
+          <div class="process-tracking">
+            <div *ngFor="let process of pendingProcesses()" class="process-item">
+              <div class="process-header">
+                <span class="process-title">
+                  {{process.type === 'hire' ? '待审核入职申请' : '待办理离职手续'}}
+                </span>
+                <span class="process-count">{{process.count}}个</span>
+              </div>
+              <mat-progress-bar [value]="process.progress" color="primary"></mat-progress-bar>
+              <button mat-button color="primary" class="process-action" (click)="navigateToProcess(process.type)">
+                查看详情
+              </button>
+            </div>
+          </div>
+        </div>
+
+        <div class="card-section">
+          <h3>风险预警</h3>
+          <div class="risk-alerts">
+            <div *ngFor="let alert of riskAlerts()" class="risk-alert">
+              <div class="alert-header">
+                <mat-icon color="warn">warning</mat-icon>
+                <span class="alert-title">{{alert.type}}</span>
+                <span class="alert-count">{{alert.count}}人</span>
+              </div>
+              <div class="alert-employees">
+                <div *ngFor="let employee of alert.employees" class="employee-item">
+                  <span class="employee-name">{{employee.name}}</span>
+                  <span class="employee-position">{{employee.position}}</span>
+                  <span class="employee-department">{{employee.department}}</span>
+                  <span *ngIf="employee.daysLeft" class="days-left">剩余{{employee.daysLeft}}天</span>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </mat-card-content>
+    </mat-card>
+
+    <!-- 绩效总览看板 -->
+    <mat-card class="dashboard-card">
+      <mat-card-header>
+        <mat-card-title>
+          <div class="card-title-container">
+            <mat-icon>assessment</mat-icon>
+            <span>绩效总览看板</span>
+          </div>
+        </mat-card-title>
+      </mat-card-header>
+      <mat-card-content>
+        <div class="card-section">
+          <h3>核心指标展示</h3>
+          <div class="metrics-container">
+            <div *ngFor="let metric of performanceMetrics()" class="metric-item">
+              <div class="metric-header">
+                <span class="metric-name">{{metric.name}}</span>
+                <div class="metric-values">
+                  <span class="metric-actual">{{metric.actual}}{{metric.unit}}</span>
+                  <span class="metric-target">目标: {{metric.target}}{{metric.unit}}</span>
+                </div>
+              </div>
+              <mat-progress-bar 
+                [value]="(metric.actual / metric.target) * 100" 
+                [color]="getProgressColor(metric.actual, metric.target)">
+              </mat-progress-bar>
+            </div>
+          </div>
+
+          <div class="department-performance">
+            <h4>部门绩效对比</h4>
+            <div class="department-table">
+              <div class="table-header">
+                <div class="table-cell">部门</div>
+                <div class="table-cell">优秀作品率</div>
+                <div class="table-cell">准时交付率</div>
+                <div class="table-cell">客户满意度</div>
+              </div>
+              <div *ngFor="let dept of departmentPerformance()" class="table-row">
+                <div class="table-cell">{{dept.name}}</div>
+                <div class="table-cell">{{dept.excellentWorkRate}}%</div>
+                <div class="table-cell">{{dept.deliveryOnTimeRate}}%</div>
+                <div class="table-cell">{{dept.customerSatisfaction}}</div>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <div class="card-section">
+          <h3>绩效等级分布</h3>
+          <!-- 用 ECharts 饼图替换占位块 -->
+          <div id="hrPerformanceDistributionChart" class="chart-container"></div>
+        </div>
+
+        <div class="card-section">
+          <h3>扣分项预警</h3>
+          <div class="penalty-warnings">
+            <div *ngFor="let warning of penaltyWarnings()" class="warning-item">
+              <div class="warning-header">
+                <mat-icon color="warn">error</mat-icon>
+                <span class="warning-issue">{{warning.issue}}</span>
+              </div>
+              <div class="warning-details">
+                <div class="warning-departments">
+                  <h4>高发部门</h4>
+                  <div *ngFor="let dept of warning.departments" class="department-item">
+                    <span class="department-name">{{dept.name}}</span>
+                    <span class="department-count">{{dept.count}}次</span>
+                  </div>
+                </div>
+                <div class="warning-employees">
+                  <h4>高发个人</h4>
+                  <div *ngFor="let emp of warning.employees" class="employee-item">
+                    <span class="employee-name">{{emp.name}}</span>
+                    <span class="employee-department">{{emp.department}}</span>
+                    <span class="employee-count">{{emp.count}}次</span>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </mat-card-content>
+    </mat-card>
+
+    <!-- 关键数据看板 -->
+    <mat-card class="dashboard-card">
+      <mat-card-header>
+        <mat-card-title>
+          <div class="card-title-container">
+            <mat-icon>insights</mat-icon>
+            <span>关键数据看板</span>
+          </div>
+        </mat-card-title>
+      </mat-card-header>
+      <mat-card-content>
+        <div class="card-section">
+          <h3>员工结构分析</h3>
+          <div class="structure-analysis">
+            <mat-tab-group>
+              <mat-tab *ngFor="let structure of employeeStructures()" [label]="structure.category">
+                <div class="structure-data">
+                  <div *ngFor="let item of structure.data" class="structure-item">
+                    <div class="structure-header">
+                      <span class="structure-name">{{item.name}}</span>
+                      <span class="structure-count">{{item.count}}人</span>
+                    </div>
+                    <mat-progress-bar value="{{item.percentage}}" color="primary"></mat-progress-bar>
+                    <span class="structure-percentage">{{item.percentage}}%</span>
+                  </div>
+                </div>
+              </mat-tab>
+            </mat-tab-group>
+          </div>
+        </div>
+
+        <div class="card-section">
+          <h3>异动趋势</h3>
+          <!-- 用 ECharts 折线图替换占位块 -->
+          <div id="hrTrendChart" class="chart-container"></div>
+        </div>
+
+        <div class="card-section">
+          <h3>待办事项提醒</h3>
+          <div class="todo-items">
+            <div *ngFor="let item of todoItems()" class="todo-item" [ngClass]="getPriorityClass(item.priority)">
+              <div class="todo-header">
+                <span class="todo-task">{{item.task}}</span>
+                <mat-chip-set>
+                  <mat-chip>{{item.type}}</mat-chip>
+                </mat-chip-set>
+              </div>
+              <div class="todo-footer">
+                <span class="todo-due-date">截止日期: {{item.dueDate | date:'yyyy-MM-dd'}}</span>
+                <span class="todo-priority">{{item.priority === 'high' ? '紧急' : item.priority === 'medium' ? '中等' : '普通'}}</span>
+              </div>
+            </div>
+          </div>
+        </div>
+      </mat-card-content>
+    </mat-card>
+  </div>
+</div>

+ 704 - 0
src/app/pages/hr/dashboard/dashboard.scss

@@ -0,0 +1,704 @@
+.dashboard-container {
+  padding: 20px;
+}
+
+.page-header {
+  margin-bottom: 24px;
+  
+  h1 {
+    margin: 0 0 8px 0;
+    font-size: 28px;
+    font-weight: 700; /* 提升标题权重以贴近 Admin 风格 */
+    background: linear-gradient(135deg, #165DFF, #7c3aed);
+    -webkit-background-clip: text;
+    -webkit-text-fill-color: transparent;
+    background-clip: text;
+  }
+  
+  .page-description {
+    margin: 0;
+    color: #4E5969; /* 更接近 Admin 次级文字色 */
+    font-size: 16px;
+  }
+}
+
+.filter-bar {
+  display: flex;
+  align-items: center;
+  margin-bottom: 24px;
+  background-color: white;
+  padding: 12px 16px;
+  border-radius: 8px;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
+  
+  .time-filter {
+    display: flex;
+    align-items: center;
+    
+    .filter-label {
+      margin-right: 12px;
+      color: #616161;
+    }
+    
+    .filter-buttons {
+      display: flex;
+      
+      button {
+        min-width: 40px;
+        border-radius: 4px;
+        margin-right: 8px;
+        
+        &.active {
+          background-color: #1a3a6e;
+          color: white;
+        }
+      }
+    }
+  }
+}
+
+.dashboard-content {
+  display: grid;
+  grid-template-columns: 1fr;
+  gap: 24px;
+  
+  @media (min-width: 1200px) {
+    grid-template-columns: repeat(2, 1fr);
+    
+    .dashboard-card:last-child {
+      grid-column: span 2;
+    }
+  }
+}
+
+.dashboard-card {
+  border-radius: 12px; /* 统一圆角 */
+  background: #FFFFFF;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.06); /* $shadow-sm */
+  border: 1px solid rgba(229, 230, 235, 0.8);
+  overflow: hidden;
+  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+  
+  mat-card-header {
+    background-color: #FFFFFF; /* 与 Admin 统一 */
+    padding: 16px 20px;
+    border-bottom: 1px solid #E5E6EB;
+  }
+  
+  .chart-container {
+    width: 100%;
+    height: 320px;
+  }
+  
+  .card-title-container {
+    display: flex;
+    align-items: center;
+    
+    mat-icon {
+      margin-right: 10px;
+      color: #165DFF; /* 主色调 */
+    }
+    
+    span {
+      font-size: 18px;
+      font-weight: 600;
+      color: #1D2129; /* 标题主文字色 */
+    }
+  }
+  
+  mat-card-content {
+    padding: 20px;
+  }
+  
+  &:hover {
+    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); /* $shadow-md */
+    transform: translateY(-4px);
+    border-color: rgba(22, 93, 255, 0.3);
+  }
+}
+
+.card-section {
+  margin-bottom: 24px;
+  
+  &:last-child {
+    margin-bottom: 0;
+  }
+  
+  h3 {
+    margin: 0 0 16px 0;
+    font-size: 16px;
+    font-weight: 600;
+    color: #4E5969;
+    border-left: 4px solid #165DFF; /* 与 Admin 主色一致 */
+    padding-left: 8px;
+  }
+  
+  h4 {
+    margin: 16px 0 8px 0;
+    font-size: 14px;
+    font-weight: 500;
+    color: #616161;
+  }
+}
+
+/* 入职离职看板样式 */
+.data-visualization {
+  display: flex;
+  flex-direction: column;
+  height: 200px;
+  
+  .chart-placeholder {
+    display: flex;
+    justify-content: space-between;
+    align-items: flex-end;
+    height: 160px;
+    margin-bottom: 16px;
+    
+    .chart-bar-container {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      flex: 1;
+      
+      .chart-label {
+        margin-top: 8px;
+        font-size: 12px;
+        color: #757575;
+      }
+      
+      .chart-bars {
+        display: flex;
+        align-items: flex-end;
+        height: 100%;
+        width: 100%;
+        justify-content: center;
+        gap: 4px;
+        
+        .chart-bar {
+          width: 20px;
+          position: relative;
+          border-radius: 4px 4px 0 0;
+          
+          &.hired {
+            background-color: #4caf50;
+          }
+          
+          &.resigned {
+            background-color: #f44336;
+          }
+          
+          .bar-value {
+            position: absolute;
+            top: -20px;
+            left: 50%;
+            transform: translateX(-50%);
+            font-size: 12px;
+            color: #616161;
+          }
+        }
+      }
+    }
+  }
+  
+  .chart-legend {
+    display: flex;
+    justify-content: center;
+    gap: 16px;
+    
+    .legend-item {
+      display: flex;
+      align-items: center;
+      
+      .legend-color {
+        width: 12px;
+        height: 12px;
+        margin-right: 4px;
+        border-radius: 2px;
+        
+        &.hired {
+          background-color: #4caf50;
+        }
+        
+        &.resigned {
+          background-color: #f44336;
+        }
+        
+        &.top-tier {
+          background-color: #4caf50;
+        }
+        
+        &.mid-tier {
+          background-color: #2196f3;
+        }
+        
+        &.bottom-tier {
+          background-color: #f44336;
+        }
+      }
+      
+      span {
+        font-size: 12px;
+        color: #616161;
+      }
+    }
+  }
+}
+
+.process-tracking {
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+  
+  .process-item {
+    background-color: #f5f5f5;
+    padding: 12px;
+    border-radius: 4px;
+    
+    .process-header {
+      display: flex;
+      justify-content: space-between;
+      margin-bottom: 8px;
+      
+      .process-title {
+        font-weight: 500;
+        color: #424242;
+      }
+      
+      .process-count {
+        font-weight: 500;
+        color: #1a3a6e;
+      }
+    }
+    
+    mat-progress-bar {
+      margin-bottom: 8px;
+    }
+    
+    .process-action {
+      width: 100%;
+    }
+  }
+}
+
+.risk-alerts {
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+  
+  .risk-alert {
+    background-color: #fff8e1;
+    padding: 12px;
+    border-radius: 4px;
+    border-left: 4px solid #ffc107;
+    
+    .alert-header {
+      display: flex;
+      align-items: center;
+      margin-bottom: 8px;
+      
+      mat-icon {
+        margin-right: 8px;
+        font-size: 20px;
+        height: 20px;
+        width: 20px;
+      }
+      
+      .alert-title {
+        flex: 1;
+        font-weight: 500;
+        color: #424242;
+      }
+      
+      .alert-count {
+        font-weight: 500;
+        color: #f44336;
+      }
+    }
+    
+    .alert-employees {
+      display: flex;
+      flex-direction: column;
+      gap: 8px;
+      
+      .employee-item {
+        display: flex;
+        align-items: center;
+        background-color: white;
+        padding: 8px;
+        border-radius: 4px;
+        
+        .employee-name {
+          font-weight: 500;
+          margin-right: 8px;
+        }
+        
+        .employee-position,
+        .employee-department {
+          color: #757575;
+          margin-right: 8px;
+          font-size: 12px;
+        }
+        
+        .days-left {
+          margin-left: auto;
+          background-color: #f44336;
+          color: white;
+          padding: 2px 6px;
+          border-radius: 4px;
+          font-size: 12px;
+        }
+      }
+    }
+  }
+}
+
+/* 绩效总览看板样式 */
+.metrics-container {
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+  
+  .metric-item {
+    .metric-header {
+      display: flex;
+      justify-content: space-between;
+      margin-bottom: 8px;
+      
+      .metric-name {
+        font-weight: 500;
+        color: #424242;
+      }
+      
+      .metric-values {
+        display: flex;
+        gap: 8px;
+        
+        .metric-actual {
+          font-weight: 500;
+          color: #1a3a6e;
+        }
+        
+        .metric-target {
+          color: #757575;
+          font-size: 12px;
+        }
+      }
+    }
+  }
+}
+
+.department-table {
+  width: 100%;
+  border-collapse: collapse;
+  margin-top: 16px;
+  
+  .table-header {
+    display: flex;
+    background-color: #f5f5f5;
+    font-weight: 500;
+    color: #424242;
+  }
+  
+  .table-row {
+    display: flex;
+    border-bottom: 1px solid #e0e0e0;
+    
+    &:last-child {
+      border-bottom: none;
+    }
+  }
+  
+  .table-cell {
+    flex: 1;
+    padding: 8px;
+    text-align: center;
+    
+    &:first-child {
+      text-align: left;
+    }
+  }
+}
+
+.performance-distribution {
+  .pie-chart-placeholder {
+    display: flex;
+    height: 40px;
+    border-radius: 20px;
+    overflow: hidden;
+    margin-bottom: 16px;
+    
+    .pie-segment {
+      height: 100%;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      position: relative;
+      
+      &.top-tier {
+        background-color: #4caf50;
+      }
+      
+      &.mid-tier {
+        background-color: #2196f3;
+      }
+      
+      &.bottom-tier {
+        background-color: #f44336;
+      }
+      
+      .segment-label,
+      .segment-value {
+        color: white;
+        font-size: 12px;
+        font-weight: 500;
+        margin: 0 4px;
+      }
+    }
+  }
+}
+
+.penalty-warnings {
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+  
+  .warning-item {
+    background-color: #f5f5f5;
+    padding: 12px;
+    border-radius: 4px;
+    
+    .warning-header {
+      display: flex;
+      align-items: center;
+      margin-bottom: 12px;
+      
+      mat-icon {
+        margin-right: 8px;
+        font-size: 20px;
+        height: 20px;
+        width: 20px;
+      }
+      
+      .warning-issue {
+        font-weight: 500;
+        color: #424242;
+      }
+    }
+    
+    .warning-details {
+      display: flex;
+      gap: 24px;
+      
+      .warning-departments,
+      .warning-employees {
+        flex: 1;
+        
+        h4 {
+          margin-top: 0;
+        }
+        
+        .department-item,
+        .employee-item {
+          display: flex;
+          justify-content: space-between;
+          padding: 4px 0;
+          
+          .department-name,
+          .employee-name {
+            font-weight: 500;
+          }
+          
+          .department-count,
+          .employee-count {
+            color: #f44336;
+            font-weight: 500;
+          }
+          
+          .employee-department {
+            color: #757575;
+            font-size: 12px;
+            margin-left: 8px;
+          }
+        }
+      }
+    }
+  }
+}
+
+/* 关键数据看板样式 */
+.structure-data {
+  padding: 16px 0;
+  
+  .structure-item {
+    margin-bottom: 16px;
+    
+    &:last-child {
+      margin-bottom: 0;
+    }
+    
+    .structure-header {
+      display: flex;
+      justify-content: space-between;
+      margin-bottom: 8px;
+      
+      .structure-name {
+        font-weight: 500;
+        color: #424242;
+      }
+      
+      .structure-count {
+        font-weight: 500;
+        color: #1a3a6e;
+      }
+    }
+    
+    .structure-percentage {
+      display: block;
+      text-align: right;
+      margin-top: 4px;
+      font-size: 12px;
+      color: #757575;
+    }
+  }
+}
+
+.movement-trends {
+  height: 200px;
+  
+  .line-chart-placeholder {
+    position: relative;
+    height: 160px;
+    margin-bottom: 16px;
+    border-bottom: 1px solid #e0e0e0;
+    border-left: 1px solid #e0e0e0;
+    
+    .chart-lines {
+      position: absolute;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      
+      .chart-line {
+        position: absolute;
+        height: 2px;
+        
+        &.hired {
+          background-color: #4caf50;
+          width: 100%;
+          top: 30%;
+        }
+        
+        &.resigned {
+          background-color: #f44336;
+          width: 100%;
+          top: 60%;
+        }
+      }
+    }
+    
+    .chart-points {
+      position: absolute;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      
+      .chart-point-group {
+        position: absolute;
+        bottom: 0;
+        transform: translateX(-50%);
+        
+        .chart-point {
+          position: absolute;
+          width: 8px;
+          height: 8px;
+          border-radius: 50%;
+          transform: translate(-50%, 50%);
+          
+          &.hired {
+            background-color: #4caf50;
+          }
+          
+          &.resigned {
+            background-color: #f44336;
+          }
+        }
+        
+        .chart-label {
+          position: absolute;
+          bottom: -20px;
+          left: 50%;
+          transform: translateX(-50%);
+          font-size: 12px;
+          color: #757575;
+        }
+      }
+    }
+  }
+}
+
+.todo-items {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+  
+  .todo-item {
+    background-color: #f5f5f5;
+    padding: 12px;
+    border-radius: 4px;
+    border-left: 4px solid #9e9e9e;
+    
+    &.priority-high {
+      border-left-color: #f44336;
+    }
+    
+    &.priority-medium {
+      border-left-color: #ff9800;
+    }
+    
+    &.priority-low {
+      border-left-color: #4caf50;
+    }
+    
+    .todo-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: flex-start;
+      margin-bottom: 8px;
+      
+      .todo-task {
+        font-weight: 500;
+        color: #424242;
+        margin-right: 8px;
+      }
+    }
+    
+    .todo-footer {
+      display: flex;
+      justify-content: space-between;
+      
+      .todo-due-date {
+        font-size: 12px;
+        color: #757575;
+      }
+      
+      .todo-priority {
+        font-size: 12px;
+        font-weight: 500;
+        
+        .priority-high & {
+          color: #f44336;
+        }
+        
+        .priority-medium & {
+          color: #ff9800;
+        }
+        
+        .priority-low & {
+          color: #4caf50;
+        }
+      }
+    }
+  }
+}

+ 381 - 0
src/app/pages/hr/dashboard/dashboard.ts

@@ -0,0 +1,381 @@
+import { Component, OnInit, AfterViewInit, OnDestroy, signal } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { RouterModule } from '@angular/router';
+import { MatCardModule } from '@angular/material/card';
+import { MatButtonModule } from '@angular/material/button';
+import { MatIconModule } from '@angular/material/icon';
+import { MatTabsModule } from '@angular/material/tabs';
+import { MatProgressBarModule } from '@angular/material/progress-bar';
+import { MatChipsModule } from '@angular/material/chips';
+import { MatSelectModule } from '@angular/material/select';
+import { FormsModule } from '@angular/forms';
+
+// 模拟数据接口
+interface EmployeeMovement {
+  month: string;
+  hired: number;
+  resigned: number;
+  turnoverRate: number;
+}
+
+interface DepartmentData {
+  name: string;
+  hired: number;
+  resigned: number;
+  turnoverRate: number;
+}
+
+interface PendingProcess {
+  type: 'hire' | 'resign';
+  count: number;
+  progress: number;
+}
+
+interface RiskAlert {
+  type: string;
+  employees: {name: string; position: string; department: string; daysLeft?: number}[];
+  count: number;
+}
+
+interface PerformanceMetric {
+  name: string;
+  target: number;
+  actual: number;
+  unit: string;
+}
+
+interface DepartmentPerformance {
+  name: string;
+  excellentWorkRate: number;
+  deliveryOnTimeRate: number;
+  customerSatisfaction: number;
+}
+
+interface PerformanceTier {
+  name: string;
+  percentage: number;
+  count: number;
+}
+
+interface PenaltyWarning {
+  issue: string;
+  departments: {name: string; count: number}[];
+  employees: {name: string; department: string; count: number}[];
+}
+
+interface EmployeeStructure {
+  category: string;
+  data: {name: string; count: number; percentage: number}[];
+}
+
+interface MovementTrend {
+  month: string;
+  hired: number;
+  resigned: number;
+}
+
+interface TodoItem {
+  task: string;
+  dueDate: Date;
+  priority: 'high' | 'medium' | 'low';
+  type: string;
+}
+
+@Component({
+  selector: 'app-hr-dashboard',
+  standalone: true,
+  imports: [
+    CommonModule,
+    RouterModule,
+    MatCardModule,
+    MatButtonModule,
+    MatIconModule,
+    MatTabsModule,
+    MatProgressBarModule,
+    MatChipsModule,
+    MatSelectModule,
+    FormsModule
+  ],
+  templateUrl: './dashboard.html',
+  styleUrls: ['./dashboard.scss']
+})
+export class Dashboard implements OnInit, AfterViewInit, OnDestroy {
+  // 时间筛选
+  timeFilter = signal('month');
+
+  // 图表实例
+  private movementChart: any | null = null;
+  private performanceChart: any | null = null;
+  private trendChart: any | null = null;
+  // 入职离职数据
+  employeeMovements = signal<EmployeeMovement[]>([
+    { month: '1月', hired: 12, resigned: 5, turnoverRate: 3.2 },
+    { month: '2月', hired: 8, resigned: 4, turnoverRate: 2.5 },
+    { month: '3月', hired: 15, resigned: 6, turnoverRate: 3.8 },
+    { month: '4月', hired: 10, resigned: 8, turnoverRate: 5.1 },
+    { month: '5月', hired: 14, resigned: 7, turnoverRate: 4.2 },
+    { month: '6月', hired: 18, resigned: 5, turnoverRate: 2.9 }
+  ]);
+  
+  departmentData = signal<DepartmentData[]>([
+    { name: '设计部', hired: 8, resigned: 3, turnoverRate: 4.2 },
+    { name: '客服部', hired: 5, resigned: 2, turnoverRate: 3.1 },
+    { name: '技术部', hired: 12, resigned: 4, turnoverRate: 5.3 },
+    { name: '行政部', hired: 3, resigned: 1, turnoverRate: 2.8 }
+  ]);
+  
+  pendingProcesses = signal<PendingProcess[]>([
+    { type: 'hire', count: 8, progress: 65 },
+    { type: 'resign', count: 3, progress: 40 }
+  ]);
+  
+  riskAlerts = signal<RiskAlert[]>([
+    { 
+      type: '试用期即将到期', 
+      employees: [
+        { name: '张三', position: '设计师', department: '设计部', daysLeft: 7 },
+        { name: '李四', position: '客服专员', department: '客服部', daysLeft: 10 },
+        { name: '王五', position: '前端开发', department: '技术部', daysLeft: 15 }
+      ],
+      count: 3
+    },
+    { 
+      type: '离职关键岗位', 
+      employees: [
+        { name: '赵六', position: '高级设计师', department: '设计部' },
+        { name: '钱七', position: '技术组长', department: '技术部' }
+      ],
+      count: 2
+    }
+  ]);
+  
+  // 绩效数据
+  performanceMetrics = signal<PerformanceMetric[]>([
+    { name: '优秀作品率', target: 80, actual: 76, unit: '%' },
+    { name: '项目交付准时率', target: 95, actual: 92, unit: '%' },
+    { name: '客户满意度评分', target: 4.5, actual: 4.3, unit: '分' }
+  ]);
+  
+  departmentPerformance = signal<DepartmentPerformance[]>([
+    { name: '设计部', excellentWorkRate: 78, deliveryOnTimeRate: 94, customerSatisfaction: 4.4 },
+    { name: '客服部', excellentWorkRate: 82, deliveryOnTimeRate: 96, customerSatisfaction: 4.6 },
+    { name: '技术部', excellentWorkRate: 75, deliveryOnTimeRate: 90, customerSatisfaction: 4.2 },
+    { name: '行政部', excellentWorkRate: 80, deliveryOnTimeRate: 95, customerSatisfaction: 4.5 }
+  ]);
+  
+  performanceTiers = signal<PerformanceTier[]>([
+    { name: '头部20%', percentage: 20, count: 24 },
+    { name: '中部70%', percentage: 70, count: 84 },
+    { name: '尾部10%', percentage: 10, count: 12 }
+  ]);
+  
+  penaltyWarnings = signal<PenaltyWarning[]>([
+    { 
+      issue: '项目延期', 
+      departments: [
+        { name: '设计部', count: 5 },
+        { name: '技术部', count: 3 }
+      ],
+      employees: [
+        { name: '张三', department: '设计部', count: 2 },
+        { name: '李四', department: '技术部', count: 2 }
+      ]
+    },
+    { 
+      issue: '投诉差评', 
+      departments: [
+        { name: '客服部', count: 4 },
+        { name: '设计部', count: 2 }
+      ],
+      employees: [
+        { name: '王五', department: '客服部', count: 3 },
+        { name: '赵六', department: '设计部', count: 1 }
+      ]
+    }
+  ]);
+  
+  // 关键数据
+  employeeStructures = signal<EmployeeStructure[]>([
+    {
+      category: '技术等级',
+      data: [
+        { name: '初级', count: 45, percentage: 37.5 },
+        { name: '中级', count: 55, percentage: 45.8 },
+        { name: '高级', count: 20, percentage: 16.7 }
+      ]
+    },
+    {
+      category: '岗位类型',
+      data: [
+        { name: '设计师', count: 60, percentage: 50 },
+        { name: '客服', count: 30, percentage: 25 },
+        { name: '技术组长', count: 15, percentage: 12.5 },
+        { name: '其他', count: 15, percentage: 12.5 }
+      ]
+    },
+    {
+      category: '部门',
+      data: [
+        { name: '设计部', count: 65, percentage: 54.2 },
+        { name: '客服部', count: 35, percentage: 29.2 },
+        { name: '技术部', count: 20, percentage: 16.6 }
+      ]
+    }
+  ]);
+  
+  movementTrends = signal<MovementTrend[]>([
+    { month: '1月', hired: 12, resigned: 5 },
+    { month: '2月', hired: 8, resigned: 4 },
+    { month: '3月', hired: 15, resigned: 6 },
+    { month: '4月', hired: 10, resigned: 8 },
+    { month: '5月', hired: 14, resigned: 7 },
+    { month: '6月', hired: 18, resigned: 5 }
+  ]);
+  
+  todoItems = signal<TodoItem[]>([
+    { task: '审核张三绩效表', dueDate: new Date('2023-06-15'), priority: 'high', type: '绩效审核' },
+    { task: '签署李四离职协议', dueDate: new Date('2023-06-12'), priority: 'high', type: '离职手续' },
+    { task: '跟进王五试用期评估', dueDate: new Date('2023-06-20'), priority: 'medium', type: '试用期评估' },
+    { task: '审核新员工入职材料', dueDate: new Date('2023-06-18'), priority: 'medium', type: '入职手续' },
+    { task: '更新部门人员架构图', dueDate: new Date('2023-06-25'), priority: 'low', type: '文档更新' }
+  ]);
+  
+  ngOnInit() {
+    // 实际项目中这里会调用服务获取数据
+  }
+  
+  ngAfterViewInit(): void {
+    this.initCharts();
+    window.addEventListener('resize', this.handleResize);
+  }
+  
+  ngOnDestroy(): void {
+    window.removeEventListener('resize', this.handleResize);
+    this.disposeCharts();
+  }
+  
+  // 切换时间维度
+  changeTimeFilter(filter: string) {
+    this.timeFilter.set(filter);
+    // 实际项目中这里会根据筛选条件重新获取数据
+    // 刷新图表
+    this.initCharts();
+  }
+  
+  // 跳转到对应处理页面
+  navigateToProcess(type: 'hire' | 'resign') {
+    // 实际项目中这里会导航到对应页面
+    console.log(`Navigate to ${type} process page`);
+  }
+  
+  // 获取进度条颜色
+  getProgressColor(value: number, target: number): string {
+    const percentage = (value / target) * 100;
+    if (percentage >= 90) return 'primary';
+    if (percentage >= 70) return 'accent';
+    return 'warn';
+  }
+  
+  // 获取优先级样式类
+  getPriorityClass(priority: string): string {
+    switch (priority) {
+      case 'high': return 'priority-high';
+      case 'medium': return 'priority-medium';
+      case 'low': return 'priority-low';
+      default: return '';
+    }
+  }
+  
+  // ====== 图表相关 ======
+  private initCharts(): void {
+    this.initMovementChart();
+    this.initPerformanceDistributionChart();
+    this.initTrendChart();
+  }
+  
+  private disposeCharts(): void {
+    if (this.movementChart) { this.movementChart.dispose(); this.movementChart = null; }
+    if (this.performanceChart) { this.performanceChart.dispose(); this.performanceChart = null; }
+    if (this.trendChart) { this.trendChart.dispose(); this.trendChart = null; }
+  }
+  
+  private initMovementChart(): void {
+    const el = document.getElementById('hrMovementChart');
+    if (!el) return;
+    this.movementChart?.dispose();
+    // 使用全局 echarts 变量
+    // @ts-ignore
+    this.movementChart = echarts.init(el);
+  
+    const data = this.employeeMovements();
+    const months = data.map(d => d.month);
+    const hired = data.map(d => d.hired);
+    const resigned = data.map(d => d.resigned);
+  
+    this.movementChart.setOption({
+      title: { text: '月度入职/离职', left: 'center', textStyle: { fontSize: 16 } },
+      tooltip: { trigger: 'axis' },
+      legend: { data: ['入职', '离职'] },
+      grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
+      xAxis: { type: 'category', data: months },
+      yAxis: { type: 'value' },
+      series: [
+        { name: '入职', type: 'bar', data: hired, itemStyle: { color: '#165DFF' }, barGap: 0 },
+        { name: '离职', type: 'bar', data: resigned, itemStyle: { color: '#F53F3F' } }
+      ]
+    });
+  }
+  
+  private initPerformanceDistributionChart(): void {
+    const el = document.getElementById('hrPerformanceDistributionChart');
+    if (!el) return;
+    this.performanceChart?.dispose();
+    // @ts-ignore
+    this.performanceChart = echarts.init(el);
+  
+    const tiers = this.performanceTiers();
+    const data = tiers.map(t => ({ value: t.count, name: `${t.name}(${t.count}人)` }));
+  
+    this.performanceChart.setOption({
+      title: { text: '绩效等级分布', left: 'center', textStyle: { fontSize: 16 } },
+      tooltip: { trigger: 'item', formatter: '{b}: {c} 人 ({d}%)' },
+      legend: { orient: 'horizontal', bottom: 0 },
+      series: [{
+        type: 'pie', radius: '65%', center: ['50%', '50%'],
+        data,
+        emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0,0,0,0.3)' } }
+      }]
+    });
+  }
+  
+  private initTrendChart(): void {
+    const el = document.getElementById('hrTrendChart');
+    if (!el) return;
+    this.trendChart?.dispose();
+    // @ts-ignore
+    this.trendChart = echarts.init(el);
+  
+    const trend = this.movementTrends();
+    const months = trend.map(t => t.month);
+    const hired = trend.map(t => t.hired);
+    const resigned = trend.map(t => t.resigned);
+  
+    this.trendChart.setOption({
+      title: { text: '异动趋势', left: 'center', textStyle: { fontSize: 16 } },
+      tooltip: { trigger: 'axis' },
+      legend: { data: ['入职', '离职'] },
+      xAxis: { type: 'category', data: months },
+      yAxis: { type: 'value' },
+      series: [
+        { name: '入职', type: 'line', data: hired, smooth: true, lineStyle: { color: '#00B42A' }, itemStyle: { color: '#00B42A' } },
+        { name: '离职', type: 'line', data: resigned, smooth: true, lineStyle: { color: '#F53F3F' }, itemStyle: { color: '#F53F3F' } }
+      ]
+    });
+  }
+  
+  private handleResize = (): void => {
+    this.movementChart?.resize();
+    this.performanceChart?.resize();
+    this.trendChart?.resize();
+  };
+}

+ 370 - 300
src/app/pages/hr/designer-profile/designer-profile.html

@@ -1,341 +1,411 @@
-<div class="designer-profile-container">
-  <header class="page-header">
-    <h1>设计师档案</h1>
-    <p class="page-description">查看设计师详细信息、作品集和项目历史</p>
-  </header>
-
-  <!-- 主内容区 -->
-  <div class="main-content">
-    <!-- 左侧:设计师信息概览区 -->
-    <div class="profile-sidebar">
-      <!-- 设计师基本信息 -->
-      <div class="profile-card">
-        <div class="avatar-container">
-          <img [src]="designer().avatar" alt="设计师头像" class="designer-avatar">
-          <div class="status-indicator online"></div>
-        </div>
-        <div class="profile-info">
-          <h2 class="designer-name">{{ designer().name }}</h2>
-          <p class="designer-position">{{ designer().position }}</p>
-          <p class="designer-department">{{ designer().department }}</p>
-        </div>
-        <div class="profile-stats">
-          <div class="stat-item">
-            <div class="stat-value">{{ ratingStats().totalProjects }}</div>
-            <div class="stat-label">已完成项目</div>
-          </div>
-          <div class="stat-item">
-            <div class="stat-value">{{ ratingStats().avgRating }}</div>
-            <div class="stat-label">平均评分</div>
-          </div>
-          <div class="stat-item">
-            <div class="stat-value">{{ formatDate(designer().hireDate) }}</div>
-            <div class="stat-label">入职日期</div>
-          </div>
-        </div>
-        <div class="contact-info">
-          <div class="contact-item">
-            <mat-icon>phone</mat-icon>
-            <span>{{ designer().phone }}</span>
-          </div>
-          <div class="contact-item">
-            <mat-icon>email</mat-icon>
-            <span>{{ designer().email }}</span>
-          </div>
-        </div>
+<div class="designer-profile-container" *ngIf="designer() as d">
+  <div class="profile-header">
+    <div class="header-content">
+      <div class="avatar-section">
+        <img [src]="d.avatar || 'assets/images/default-avatar.svg'" alt="设计师头像" class="avatar">
       </div>
-      
-      <!-- 技能雷达图 -->
-      <div class="skills-card">
-        <div class="card-header">
-          <h3>专业技能</h3>
-        </div>
-        <div class="radar-chart-container">
-          <!-- 简化版雷达图表示 -->
-          <div class="radar-chart">
-            <div class="radar-grid">
-              <!-- 这里应该是实际的雷达图,由于是静态模板,使用简化表示 -->
-              <div class="radar-point" style="--x: 80%; --y: 90%;" matTooltip="3D建模 (5)"></div>
-              <div class="radar-point" style="--x: 60%; --y: 85%;" matTooltip="渲染 (5)"></div>
-              <div class="radar-point" style="--x: 40%; --y: 80%;" matTooltip="空间设计 (4)"></div>
-              <div class="radar-point" style="--x: 30%; --y: 70%;" matTooltip="色彩搭配 (5)"></div>
-              <div class="radar-point" style="--x: 40%; --y: 40%;" matTooltip="CAD绘图 (4)"></div>
-              <div class="radar-point" style="--x: 60%; --y: 30%;" matTooltip="客户沟通 (3)"></div>
-              <div class="radar-point" style="--x: 80%; --y: 35%;" matTooltip="项目管理 (3)"></div>
-              <!-- 连接线 -->
-              <div class="radar-connect"></div>
-            </div>
+      <div class="info-section">
+        <h1>{{ d.name }} <span class="position-badge">{{ d.position }}</span></h1>
+        <div class="basic-info">
+          <div class="info-item">
+            <mat-icon>business</mat-icon>
+            <span>{{ d.department }}</span>
           </div>
-        </div>
-        <div class="skills-list">
-          <div *ngFor="let skill of skills()" class="skill-item" [class]="getSkillLevelClass(skill.level)">
-            <span class="skill-name">{{ skill.name }}</span>
-            <div class="skill-level">
-              <span *ngFor="let star of getStars(skill.level)" class="star">{{ star }}</span>
-            </div>
+          <div class="info-item">
+            <mat-icon>school</mat-icon>
+            <span>{{ d.level }}设计师</span>
           </div>
-        </div>
-      </div>
-      
-      <!-- 擅长领域 -->
-      <div class="specialties-card">
-        <div class="card-header">
-          <h3>擅长领域</h3>
-        </div>
-        <div class="specialties-cloud">
-          <div *ngFor="let specialty of specialties()" class="specialty-tag" [class.selected]="selectedSkills().includes(specialty.name)" (click)="toggleSkillFilter(specialty.name)">
-            <span class="specialty-name">{{ specialty.name }}</span>
-            <span class="specialty-count">{{ specialty.count }}</span>
-            <div class="specialty-level">
-              <div class="level-indicator" [style.width]="(specialty.level / 5 * 100) + '%'"></div>
-            </div>
+          <div class="info-item">
+            <mat-icon>event</mat-icon>
+            <span>入职时间: {{ d.joinDate }}</span>
           </div>
         </div>
       </div>
-    </div>
-    
-    <!-- 右侧:作品集与项目历史区 -->
-    <div class="profile-content">
-      <!-- 标签页导航 -->
-      <div class="tabs-navigation">
-        <button 
-          mat-raised-button 
-          [class.active]="activeTab() === 'overview'"
-          (click)="switchTab('overview')"
-          class="tab-btn"
-        >
-          <mat-icon>dashboard</mat-icon>
-          概览
-        </button>
-        <button 
-          mat-raised-button 
-          [class.active]="activeTab() === 'portfolio'"
-          (click)="switchTab('portfolio')"
-          class="tab-btn"
-        >
+      <div class="action-section">
+        <button mat-raised-button color="primary" (click)="openPortfolioPreview()">
           <mat-icon>collections</mat-icon>
-          作品集
-        </button>
-        <button 
-          mat-raised-button 
-          [class.active]="activeTab() === 'projects'"
-          (click)="switchTab('projects')"
-          class="tab-btn"
-        >
-          <mat-icon>history</mat-icon>
-          项目历史
+          查看作品集
         </button>
       </div>
-      
-      <!-- 搜索和筛选 -->
-      <div class="search-filter-section">
-        <div class="search-container">
-          <mat-icon class="search-icon">search</mat-icon>
-          <input 
-            matInput 
-            placeholder="搜索作品或项目..."
-            [value]="searchTerm()"
-            (input)="searchTerm.set($event.target.value)"
-            class="search-input"
-          >
-          <button mat-icon-button *ngIf="searchTerm()" (click)="searchTerm.set('')" class="clear-search">
-            <mat-icon>close</mat-icon>
-          </button>
-        </div>
-      </div>
-      
-      <!-- 标签页内容 -->
+    </div>
+  </div>
+
+  <mat-tab-group [selectedIndex]="activeTab()" (selectedIndexChange)="changeTab($event)" animationDuration="0ms" class="profile-tabs">
+    <!-- 基础信息标签页 -->
+    <mat-tab label="基础信息">
       <div class="tab-content">
-        <!-- 概览标签页 -->
-        <div *ngIf="activeTab() === 'overview'" class="overview-content">
-          <!-- 评分统计 -->
-          <div class="rating-stats-card">
-            <div class="card-header">
-              <h3>项目评分统计</h3>
-            </div>
-            <div class="rating-stats-content">
-              <div class="overall-rating">
-                <div class="rating-value">{{ ratingStats().avgRating }}</div>
-                <div class="rating-stars">
-                  <span *ngFor="let star of getStars(ratingStats().avgRating)" class="star">{{ star }}</span>
-                </div>
-                <div class="rating-count">基于 {{ ratingStats().totalProjects }} 个已完成项目</div>
+        <div class="info-grid">
+          <!-- 技能与风格卡片 -->
+          <mat-card class="info-card skills-card">
+            <mat-card-header>
+              <mat-card-title>技能与专长</mat-card-title>
+            </mat-card-header>
+            <mat-card-content>
+              <div class="section-title">技术技能</div>
+              <div class="skill-chips">
+                <mat-chip-set>
+                  <mat-chip *ngFor="let skill of d.skills">{{ skill }}</mat-chip>
+                </mat-chip-set>
+              </div>
+              
+              <div class="section-title">擅长风格</div>
+              <div class="style-chips">
+                <mat-chip-set>
+                  <mat-chip *ngFor="let style of d.styles" color="accent" selected>{{ style }}</mat-chip>
+                </mat-chip-set>
               </div>
-              <div class="rating-breakdown">
-                <div class="rating-item">
-                  <div class="rating-label">5星</div>
-                  <div class="rating-bar">
-                    <div class="rating-fill" [style.width]="(ratingStats().fiveStarCount / ratingStats().totalProjects * 100) + '%'"></div>
+            </mat-card-content>
+          </mat-card>
+
+          <!-- 资质证书卡片 -->
+          <mat-card class="info-card certificates-card">
+            <mat-card-header>
+              <mat-card-title>资质证书</mat-card-title>
+            </mat-card-header>
+            <mat-card-content>
+              <div class="certificate-list">
+                <div class="certificate-item" *ngFor="let cert of d.certificates || []">
+                  <div class="cert-icon">
+                    <mat-icon>verified</mat-icon>
                   </div>
-                  <div class="rating-percentage">{{ Math.round(ratingStats().fiveStarCount / ratingStats().totalProjects * 100) }}%</div>
-                </div>
-                <div class="rating-item">
-                  <div class="rating-label">4星</div>
-                  <div class="rating-bar">
-                    <div class="rating-fill" [style.width]="(ratingStats().fourStarCount / ratingStats().totalProjects * 100) + '%'"></div>
+                  <div class="cert-details">
+                    <div class="cert-name">{{ cert.name }}</div>
+                    <div class="cert-meta">
+                      <span class="cert-type">{{ cert.type }}</span>
+                      <span class="cert-date">{{ cert.date }}</span>
+                    </div>
                   </div>
-                  <div class="rating-percentage">{{ Math.round(ratingStats().fourStarCount / ratingStats().totalProjects * 100) }}%</div>
                 </div>
-                <div class="rating-item">
-                  <div class="rating-label">3星及以下</div>
-                  <div class="rating-bar">
-                    <div class="rating-fill" [style.width]="(ratingStats().threeStarCount / ratingStats().totalProjects * 100) + '%'"></div>
-                  </div>
-                  <div class="rating-percentage">{{ Math.round(ratingStats().threeStarCount / ratingStats().totalProjects * 100) }}%</div>
+                <div class="add-certificate" *ngIf="(d.certificates?.length || 0) === 0">
+                  <button mat-stroked-button color="primary">
+                    <mat-icon>add</mat-icon>
+                    添加证书
+                  </button>
                 </div>
               </div>
-            </div>
-          </div>
-          
-          <!-- 最近作品 -->
-          <div class="recent-portfolio-card">
-            <div class="card-header">
-              <h3>最近作品</h3>
-              <button mat-button (click)="switchTab('portfolio')" class="view-all-btn">
-                查看全部
-                <mat-icon>chevron_right</mat-icon>
-              </button>
-            </div>
-            <div class="recent-portfolio-grid">
-              <div 
-                *ngFor="let item of portfolio().slice(0, 4)"
-                class="portfolio-item-small"
-                (click)="openPortfolioPreview(item)"
-              >
-                <img [src]="item.imageUrl" alt="{{ item.title }}" class="portfolio-image-small">
-                <div class="portfolio-info-small">
-                  <div class="portfolio-title-small">{{ item.title }}</div>
-                  <div class="portfolio-rating-small">
-                    <span *ngFor="let star of getStars(item.rating)" class="star-small">{{ star }}</span>
+            </mat-card-content>
+          </mat-card>
+
+          <!-- 空档期卡片 -->
+          <mat-card class="info-card availability-card">
+            <mat-card-header>
+              <mat-card-title>技术空档期</mat-card-title>
+            </mat-card-header>
+            <mat-card-content>
+              <div class="availability-info">
+                <div class="availability-status" [ngClass]="{'available': (d.availableDates.length || 0) > 0}">
+                  <mat-icon>{{ (d.availableDates.length || 0) > 0 ? 'check_circle' : 'schedule' }}</mat-icon>
+                  <span>{{ (d.availableDates.length || 0) > 0 ? '当前有空档期' : '当前无空档期' }}</span>
+                </div>
+                <div class="available-dates" *ngIf="(d.availableDates.length || 0) > 0">
+                  <div class="section-title">可接单日期</div>
+                  <div class="date-chips">
+                    <mat-chip-set>
+                      <mat-chip *ngFor="let date of d.availableDates || []" color="primary" selected>{{ date }}</mat-chip>
+                    </mat-chip-set>
                   </div>
                 </div>
               </div>
-            </div>
-          </div>
-          
-          <!-- 进行中项目 -->
-          <div class="in-progress-card">
-            <div class="card-header">
-              <h3>进行中项目</h3>
-              <button mat-button (click)="switchTab('projects')" class="view-all-btn">
-                查看全部
-                <mat-icon>chevron_right</mat-icon>
-              </button>
-            </div>
-            <div class="projects-list">
-              <div 
-                *ngFor="let project of inProgressProjects()"
-                class="project-item"
-              >
-                <div class="project-info">
-                  <div class="project-name">{{ project.name }}</div>
-                  <div class="project-customer">客户: {{ project.customerName }}</div>
-                  <div class="project-time">开始于: {{ formatDate(project.startDate) }}</div>
+            </mat-card-content>
+            <mat-card-actions>
+              <button mat-button color="primary">更新空档期</button>
+            </mat-card-actions>
+          </mat-card>
+
+          <!-- 权限配置卡片 -->
+          <mat-card class="info-card permissions-card">
+            <mat-card-header>
+              <mat-card-title>权限配置</mat-card-title>
+            </mat-card-header>
+            <mat-card-content>
+              <div class="permission-list">
+                <div class="permission-item">
+                  <div class="permission-name">项目数据查看权限</div>
+                  <div class="permission-status granted">已授权</div>
+                </div>
+                <div class="permission-item">
+                  <div class="permission-name">业务费用数据查看</div>
+                  <div class="permission-status denied">未授权</div>
                 </div>
-                <div class="project-role">
-                  {{ project.role }}
+                <div class="permission-item">
+                  <div class="permission-name">团队绩效数据查看</div>
+                  <div class="permission-status granted">已授权</div>
                 </div>
-                <div class="project-status" [class]="getProjectStatusClass(project.status)">
-                  {{ project.status }}
+                <div class="permission-item">
+                  <div class="permission-name">客户联系信息查看</div>
+                  <div class="permission-status granted">已授权</div>
                 </div>
               </div>
-            </div>
+            </mat-card-content>
+            <mat-card-actions>
+              <button mat-button color="primary">调整权限</button>
+            </mat-card-actions>
+          </mat-card>
+        </div>
+      </div>
+    </mat-tab>
+
+    <!-- 工作记录标签页 -->
+    <mat-tab label="工作记录">
+      <div class="tab-content">
+        <div class="project-filters">
+          <mat-chip-set>
+            <mat-chip selected>全部项目</mat-chip>
+            <mat-chip>装修公司</mat-chip>
+            <mat-chip>设计工作室</mat-chip>
+            <mat-chip>效果图设计</mat-chip>
+            <mat-chip>建模+渲染</mat-chip>
+          </mat-chip-set>
+        </div>
+
+        <table mat-table [dataSource]="d.projects || []" class="project-table">
+          <!-- 项目名称列 -->
+          <ng-container matColumnDef="name">
+            <th mat-header-cell *matHeaderCellDef>项目名称</th>
+            <td mat-cell *matCellDef="let project">
+              <div class="project-name">{{ project.name }}</div>
+              <div class="project-tags">
+                <span class="tag" *ngFor="let tag of project.tags">{{ tag }}</span>
+              </div>
+            </td>
+          </ng-container>
+
+          <!-- 客户类型列 -->
+          <ng-container matColumnDef="clientType">
+            <th mat-header-cell *matHeaderCellDef>客户类型</th>
+            <td mat-cell *matCellDef="let project">{{ project.clientType }}</td>
+          </ng-container>
+
+          <!-- 项目角色列 -->
+          <ng-container matColumnDef="role">
+            <th mat-header-cell *matHeaderCellDef>项目角色</th>
+            <td mat-cell *matCellDef="let project">{{ project.role }}</td>
+          </ng-container>
+
+          <!-- 交付日期列 -->
+          <ng-container matColumnDef="deliveryDate">
+            <th mat-header-cell *matHeaderCellDef>交付日期</th>
+            <td mat-cell *matCellDef="let project">{{ project.deliveryDate }}</td>
+          </ng-container>
+
+          <!-- 客户评分列 -->
+          <ng-container matColumnDef="clientRating">
+            <th mat-header-cell *matHeaderCellDef>客户评分</th>
+            <td mat-cell *matCellDef="let project">
+              <div class="rating-stars">
+                <mat-icon *ngFor="let star of getRatingStars(project.clientRating)">{{ star }}</mat-icon>
+                <span class="rating-value">{{ project.clientRating }}</span>
+              </div>
+            </td>
+          </ng-container>
+
+          <!-- 操作列 -->
+          <ng-container matColumnDef="actions">
+            <th mat-header-cell *matHeaderCellDef>操作</th>
+            <td mat-cell *matCellDef="let project">
+              <button mat-icon-button matTooltip="查看反馈">
+                <mat-icon>feedback</mat-icon>
+              </button>
+              <button mat-icon-button matTooltip="查看详情">
+                <mat-icon>visibility</mat-icon>
+              </button>
+            </td>
+          </ng-container>
+
+          <tr mat-header-row *matHeaderRowDef="projectColumns"></tr>
+          <tr mat-row *matRowDef="let row; columns: projectColumns;"></tr>
+        </table>
+
+        <div class="substitute-records-section">
+          <h3>代班记录</h3>
+          <div class="empty-state">
+            <mat-icon>event_busy</mat-icon>
+            <p>暂无代班记录</p>
+            <button mat-stroked-button color="primary">
+              <mat-icon>add</mat-icon>
+              添加代班记录
+            </button>
           </div>
         </div>
-        
-        <!-- 作品集标签页 -->
-        <div *ngIf="activeTab() === 'portfolio'" class="portfolio-content">
-          <div class="portfolio-grid">
-            <div 
-              *ngFor="let item of filteredPortfolio()"
-              class="portfolio-item"
-              (click)="openPortfolioPreview(item)"
-            >
-              <div class="portfolio-image-container">
-                <img [src]="item.imageUrl" alt="{{ item.title }}" class="portfolio-image">
-                <div class="portfolio-overlay">
-                  <div class="portfolio-title">{{ item.title }}</div>
-                  <div class="portfolio-rating">
-                    <span *ngFor="let star of getStars(item.rating)" class="star">{{ star }}</span>
-                  </div>
-                </div>
+        </div>
+      </mat-tab>
+
+    <!-- 绩效详情标签页 -->
+    <mat-tab label="绩效详情">
+      <div class="tab-content">
+        <ng-container *ngIf="(d.performance?.length || 0) > 0; else perfEmpty">
+          <div class="performance-summary">
+            <div class="summary-card">
+              <div class="summary-title">客户满意度</div>
+              <div class="summary-value">
+                {{ d.performance[0].customerSatisfaction }}%
+                <mat-icon class="trend-icon" [ngClass]="getPerformanceTrend('customerSatisfaction')">
+                  {{ getPerformanceTrend('customerSatisfaction') === 'up' ? 'trending_up' : 
+                     getPerformanceTrend('customerSatisfaction') === 'down' ? 'trending_down' : 'trending_flat' }}
+                </mat-icon>
               </div>
-              <div class="portfolio-details">
-                <div class="portfolio-project-name">{{ item.projectName }}</div>
-                <div class="portfolio-date">{{ formatDate(item.completionDate) }}</div>
+              <mat-progress-bar mode="determinate" [value]="d.performance[0].customerSatisfaction || 0"></mat-progress-bar>
+            </div>
+            
+            <div class="summary-card">
+              <div class="summary-title">优秀作品率</div>
+              <div class="summary-value">
+                {{ d.performance[0].excellentWorkRate }}%
+                <mat-icon class="trend-icon" [ngClass]="getPerformanceTrend('excellentWorkRate')">
+                  {{ getPerformanceTrend('excellentWorkRate') === 'up' ? 'trending_up' : 
+                     getPerformanceTrend('excellentWorkRate') === 'down' ? 'trending_down' : 'trending_flat' }}
+                </mat-icon>
               </div>
+              <mat-progress-bar mode="determinate" [value]="d.performance[0].excellentWorkRate || 0"></mat-progress-bar>
             </div>
-          </div>
-          
-          <!-- 空状态 -->
-          <div *ngIf="filteredPortfolio().length === 0" class="empty-state">
-            <div class="empty-icon">
-              <mat-icon>search_off</mat-icon>
+            
+            <div class="summary-card">
+              <div class="summary-title">业绩值</div>
+              <div class="summary-value">
+                {{ d.performance[0].performanceValue }}
+                <mat-icon class="trend-icon" [ngClass]="getPerformanceTrend('performanceValue')">
+                  {{ getPerformanceTrend('performanceValue') === 'up' ? 'trending_up' : 
+                     getPerformanceTrend('performanceValue') === 'down' ? 'trending_down' : 'trending_flat' }}
+                </mat-icon>
+              </div>
+              <mat-progress-bar mode="determinate" [value]="d.performance[0].performanceValue || 0"></mat-progress-bar>
             </div>
-            <p>没有找到符合条件的作品</p>
-            <button mat-button (click)="searchTerm.set(''); selectedSkills.set([])" class="clear-filter-btn">
-              清除筛选条件
-            </button>
           </div>
-        </div>
-        
-        <!-- 项目历史标签页 -->
-        <div *ngIf="activeTab() === 'projects'" class="projects-content">
-          <!-- 项目时间轴 -->
-          <div class="project-timeline">
-            <div 
-              *ngFor="let project of orderedProjectHistory()"
-              class="timeline-item"
-            >
-              <div class="timeline-marker" [class]="getProjectStatusClass(project.status)"></div>
-              <div class="timeline-content">
-                <div class="timeline-date">{{ formatDate(project.startDate) }} - {{ project.endDate ? formatDate(project.endDate) : '进行中' }}</div>
-                <div class="project-card">
-                  <div class="project-header">
-                    <div class="project-name">{{ project.name }}</div>
-                    <div class="project-status" [class]="getProjectStatusClass(project.status)">
-                      {{ project.status }}
-                    </div>
-                  </div>
-                  <div class="project-details">
-                    <div class="detail-row">
-                      <span class="detail-label">客户:</span>
-                      <span class="detail-value">{{ project.customerName }}</span>
-                    </div>
-                    <div class="detail-row">
-                      <span class="detail-label">担任角色:</span>
-                      <span class="detail-value">{{ project.role }}</span>
+      
+          <div class="performance-comparison">
+            <h3>绩效对比分析</h3>
+            <div class="comparison-chart">
+              <div class="chart-placeholder">
+                <div class="chart-bars">
+                  <div class="chart-bar">
+                    <div class="bar-label">客户满意度</div>
+                    <div class="bar-container">
+                      <div class="bar designer-bar" [style.height.%]="d.performance[0].customerSatisfaction || 0"></div>
+                      <div class="bar average-bar" style="height: 80%;"></div>
                     </div>
-                    <div *ngIf="project.rating" class="detail-row">
-                      <span class="detail-label">客户评分:</span>
-                      <span class="detail-value">
-                        <div class="rating">
-                          <span *ngFor="let star of getStars(project.rating)" class="star">{{ star }}</span>
-                        </div>
-                      </span>
+                    <div class="bar-legend">
+                      <div class="legend-item"><span class="legend-color designer"></span> 设计师</div>
+                      <div class="legend-item"><span class="legend-color average"></span> 部门平均</div>
                     </div>
-                    <div *ngIf="project.feedback" class="detail-row">
-                      <span class="detail-label">客户反馈:</span>
-                      <span class="detail-value feedback" (click)="viewFeedback(project)">
-                        {{ project.feedback }}
-                        <mat-icon>expand_more</mat-icon>
-                      </span>
+                  </div>
+                  <div class="chart-bar">
+                    <div class="bar-label">优秀作品率</div>
+                    <div class="bar-container">
+                      <div class="bar designer-bar" [style.height.%]="d.performance[0].excellentWorkRate || 0"></div>
+                      <div class="bar average-bar" style="height: 75%;"></div>
                     </div>
-                    <div class="detail-row">
-                      <span class="detail-label">使用技能:</span>
-                      <div class="skills-used">
-                        <span *ngFor="let skill of project.skillsUsed" class="skill-used-tag">
-                          {{ skill }}
-                        </span>
-                      </div>
+                  </div>
+                  <div class="chart-bar">
+                    <div class="bar-label">业绩值</div>
+                    <div class="bar-container">
+                      <div class="bar designer-bar" [style.height.%]="d.performance[0].performanceValue || 0"></div>
+                      <div class="bar average-bar" style="height: 78%;"></div>
                     </div>
                   </div>
                 </div>
               </div>
             </div>
           </div>
-        </div>
+      
+          <div class="performance-details">
+            <h3>绩效明细记录</h3>
+            <table mat-table [dataSource]="d.performance || []" class="performance-table">
+              <!-- 季度列 -->
+              <ng-container matColumnDef="quarter">
+                <th mat-header-cell *matHeaderCellDef>季度</th>
+                <td mat-cell *matCellDef="let item">{{ item.quarter }}</td>
+              </ng-container>
+      
+              <!-- 客户满意度列 -->
+              <ng-container matColumnDef="customerSatisfaction">
+                <th mat-header-cell *matHeaderCellDef>客户满意度</th>
+                <td mat-cell *matCellDef="let item">{{ item.customerSatisfaction }}%</td>
+              </ng-container>
+      
+              <!-- 优秀作品率列 -->
+              <ng-container matColumnDef="excellentWorkRate">
+                <th mat-header-cell *matHeaderCellDef>优秀作品率</th>
+                <td mat-cell *matCellDef="let item">{{ item.excellentWorkRate }}%</td>
+              </ng-container>
+      
+              <!-- 业绩值列 -->
+              <ng-container matColumnDef="performanceValue">
+                <th mat-header-cell *matHeaderCellDef>业绩值</th>
+                <td mat-cell *matCellDef="let item">{{ item.performanceValue }}</td>
+              </ng-container>
+      
+              <!-- 绩效等级列 -->
+              <ng-container matColumnDef="finalGrade">
+                <th mat-header-cell *matHeaderCellDef>绩效等级</th>
+                <td mat-cell *matCellDef="let item">
+                  <span class="grade-badge" [style.background-color]="getPerformanceColor(item.finalGrade)">
+                    {{ item.finalGrade }}
+                  </span>
+                </td>
+              </ng-container>
+      
+              <!-- 操作列 -->
+              <ng-container matColumnDef="actions">
+                <th mat-header-cell *matHeaderCellDef>操作</th>
+                <td mat-cell *matCellDef="let item">
+                  <button mat-icon-button matTooltip="查看扣分项" [matBadge]="item.deductions.length" 
+                    [matBadgeHidden]="item.deductions.length === 0" matBadgeColor="warn">
+                    <mat-icon>error_outline</mat-icon>
+                  </button>
+                  <button mat-icon-button matTooltip="查看详情">
+                    <mat-icon>visibility</mat-icon>
+                  </button>
+                </td>
+              </ng-container>
+      
+              <tr mat-header-row *matHeaderRowDef="performanceColumns"></tr>
+              <tr mat-row *matRowDef="let row; columns: performanceColumns;"></tr>
+            </table>
+          </div>
+      
+          <div class="promotion-history">
+            <h3>晋级记录</h3>
+            <div class="timeline">
+              <div class="timeline-item">
+                <div class="timeline-point"></div>
+                <div class="timeline-content">
+                  <div class="timeline-date">2022-01-15</div>
+                  <div class="timeline-title">晋升为高级设计师</div>
+                  <div class="timeline-description">基于优秀的项目表现和客户满意度,晋升为高级设计师</div>
+                </div>
+              </div>
+              <div class="timeline-item">
+                <div class="timeline-point"></div>
+                <div class="timeline-content">
+                  <div class="timeline-date">2021-03-10</div>
+                  <div class="timeline-title">晋升为中级设计师</div>
+                  <div class="timeline-description">完成技能认证,项目质量稳定,晋升为中级设计师</div>
+                </div>
+              </div>
+              <div class="timeline-item">
+                <div class="timeline-point"></div>
+                <div class="timeline-content">
+                  <div class="timeline-date">2020-05-15</div>
+                  <div class="timeline-title">入职</div>
+                  <div class="timeline-description">作为初级设计师加入公司</div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </ng-container>
+        <ng-template #perfEmpty>
+          <div class="empty-state">
+            <mat-icon>insights</mat-icon>
+            <p>暂无绩效数据</p>
+          </div>
+        </ng-template>
       </div>
-    </div>
-  </div>
+     </mat-tab>
+  </mat-tab-group>
+</div>
+
+<!-- 加载状态 -->
+<div class="loading-container" *ngIf="!designer()">
+  <mat-spinner diameter="40"></mat-spinner>
+  <p>加载设计师信息中...</p>
 </div>

Plik diff jest za duży
+ 420 - 995
src/app/pages/hr/designer-profile/designer-profile.scss


+ 326 - 423
src/app/pages/hr/designer-profile/designer-profile.ts

@@ -1,498 +1,401 @@
-import { Component, OnInit, signal, computed, Inject } from '@angular/core';
+import { Component, OnInit, signal, inject } from '@angular/core';
 import { CommonModule } from '@angular/common';
-import { FormsModule } from '@angular/forms';
-import { MatButtonModule } from '@angular/material/button';
+import { MatTabsModule } from '@angular/material/tabs';
 import { MatCardModule } from '@angular/material/card';
+import { MatButtonModule } from '@angular/material/button';
 import { MatIconModule } from '@angular/material/icon';
-import { MatDialogModule, MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
-import { MatTabsModule } from '@angular/material/tabs';
+import { MatChipsModule } from '@angular/material/chips';
+import { MatDividerModule } from '@angular/material/divider';
+import { MatProgressBarModule } from '@angular/material/progress-bar';
+import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
+import { MatTableModule } from '@angular/material/table';
+import { MatBadgeModule } from '@angular/material/badge';
+import { MatDialogModule, MatDialog } from '@angular/material/dialog';
 import { MatTooltipModule } from '@angular/material/tooltip';
-import { Employee, DesignerSkill, DesignerPortfolioItem } from '../../../models/hr.model';
+import { ActivatedRoute } from '@angular/router';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+
+// 模拟数据接口
+interface Designer {
+  id: string;
+  name: string;
+  avatar: string;
+  position: string;
+  level: string;
+  department: string;
+  joinDate: string;
+  skills: string[];
+  styles: string[];
+  availableDates: string[];
+  certificates: {
+    name: string;
+    date: string;
+    type: string;
+  }[];
+  projects: {
+    id: string;
+    name: string;
+    clientType: string;
+    role: string;
+    deliveryDate: string;
+    clientRating: number;
+    tags: string[];
+    feedback: string;
+  }[];
+  performance: {
+    quarter: string;
+    customerSatisfaction: number;
+    excellentWorkRate: number;
+    performanceValue: number;
+    deductions: {
+      reason: string;
+      points: number;
+      date: string;
+    }[];
+    finalGrade: string;
+  }[];
+}
 
 // 作品集预览对话框组件
 @Component({
-  selector: 'app-portfolio-preview-dialog',
+  selector: 'portfolio-preview-dialog',
   standalone: true,
-  imports: [
-    CommonModule,
-    MatButtonModule,
-    MatIconModule
-  ],
+  imports: [CommonModule, MatDialogModule, MatButtonModule, MatIconModule],
   template: `
-    <div class="dialog-header">
-      <h2>{{ portfolioItem.title }}</h2>
-      <button class="close-btn" (click)="dialogRef.close()">
-        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
-          <line x1="18" y1="6" x2="6" y2="18"></line>
-          <line x1="6" y1="6" x2="18" y2="18"></line>
-        </svg>
-      </button>
-    </div>
-    <div class="dialog-content">
-      <div class="portfolio-image-container">
-        <img [src]="portfolioItem.imageUrl" alt="{{ portfolioItem.title }}" class="portfolio-image">
-        <button mat-icon-button class="nav-btn prev-btn" (click)="navigate(-1)" [disabled]="currentIndex === 0">
-          <mat-icon>chevron_left</mat-icon>
-        </button>
-        <button mat-icon-button class="nav-btn next-btn" (click)="navigate(1)" [disabled]="currentIndex === portfolioItems.length - 1">
-          <mat-icon>chevron_right</mat-icon>
+    <div class="portfolio-dialog">
+      <div class="dialog-header">
+        <h2>作品集预览</h2>
+        <button mat-icon-button (click)="close()">
+          <mat-icon>close</mat-icon>
         </button>
       </div>
-      <div class="portfolio-details">
-        <div class="detail-item">
-          <label>项目名称:</label>
-          <span>{{ portfolioItem.projectName || '无关联项目' }}</span>
-        </div>
-        <div class="detail-item">
-          <label>完成日期:</label>
-          <span>{{ formatDate(portfolioItem.completionDate) }}</span>
-        </div>
-        <div class="detail-item">
-          <label>评分:</label>
-          <div class="rating">
-            <span *ngFor="let star of getStars()" class="star">{{ star }}</span>
-            <span class="rating-number">{{ portfolioItem.rating }}/5</span>
+      <div class="dialog-content">
+        <div class="portfolio-gallery">
+          <div class="portfolio-item" *ngFor="let item of portfolioItems">
+            <img [src]="item.imageUrl" [alt]="item.title">
+            <div class="portfolio-info">
+              <h3>{{item.title}}</h3>
+              <p>{{item.description}}</p>
+              <div class="portfolio-tags">
+                <span *ngFor="let tag of item.tags">{{tag}}</span>
+              </div>
+            </div>
           </div>
         </div>
-        <div class="detail-item">
-          <label>描述:</label>
-          <p>{{ portfolioItem.description }}</p>
-        </div>
       </div>
     </div>
   `,
   styles: [`
+    .portfolio-dialog {
+      padding: 0;
+      max-width: 900px;
+      width: 100%;
+    }
     .dialog-header {
       display: flex;
       justify-content: space-between;
       align-items: center;
-      margin-bottom: 24px;
-      padding-bottom: 16px;
-      border-bottom: 1px solid #e5e7eb;
+      padding: 16px 24px;
+      border-bottom: 1px solid #eee;
     }
-    .close-btn {
-      background: none;
-      border: none;
-      cursor: pointer;
-      color: #6b7280;
-      padding: 4px;
+    .dialog-header h2 {
+      margin: 0;
+      font-size: 20px;
+      color: #1a3a6e;
     }
     .dialog-content {
-      max-width: 90vw;
-      max-height: 80vh;
+      padding: 24px;
+      max-height: 70vh;
+      overflow-y: auto;
     }
-    .portfolio-image-container {
-      position: relative;
-      margin-bottom: 24px;
-      overflow: hidden;
-      border-radius: 8px;
-      max-height: 500px;
-      display: flex;
-      align-items: center;
-      justify-content: center;
+    .portfolio-gallery {
+      display: grid;
+      grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
+      gap: 24px;
     }
-    .portfolio-image {
-      max-width: 100%;
-      max-height: 500px;
-      object-fit: contain;
+    .portfolio-item {
       border-radius: 8px;
+      overflow: hidden;
+      box-shadow: 0 4px 8px rgba(0,0,0,0.1);
+      transition: transform 0.3s;
     }
-    .nav-btn {
-      position: absolute;
-      top: 50%;
-      transform: translateY(-50%);
-      background-color: rgba(0, 0, 0, 0.5);
-      color: white;
-      width: 40px;
-      height: 40px;
-      border-radius: 50%;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      z-index: 10;
-    }
-    .prev-btn {
-      left: 10px;
+    .portfolio-item:hover {
+      transform: translateY(-5px);
     }
-    .next-btn {
-      right: 10px;
+    .portfolio-item img {
+      width: 100%;
+      height: 180px;
+      object-fit: cover;
     }
-    .portfolio-details {
-      display: flex;
-      flex-direction: column;
-      gap: 16px;
+    .portfolio-info {
+      padding: 16px;
     }
-    .detail-item {
-      display: flex;
-      flex-direction: column;
-      gap: 4px;
+    .portfolio-info h3 {
+      margin: 0 0 8px;
+      font-size: 16px;
+      color: #1a3a6e;
     }
-    label {
-      font-weight: 500;
-      color: #374151;
+    .portfolio-info p {
+      margin: 0 0 12px;
       font-size: 14px;
+      color: #666;
     }
-    span {
-      color: #4b5563;
-      font-size: 16px;
-    }
-    .rating {
+    .portfolio-tags {
       display: flex;
-      align-items: center;
+      flex-wrap: wrap;
       gap: 8px;
     }
-    .star {
-      color: #f59e0b;
-      font-size: 16px;
-    }
-    .rating-number {
-      font-size: 14px;
-      color: #6b7280;
-    }
-    p {
-      color: #4b5563;
-      line-height: 1.6;
+    .portfolio-tags span {
+      background-color: #e6f7ff;
+      color: #1a3a6e;
+      padding: 4px 8px;
+      border-radius: 4px;
+      font-size: 12px;
     }
   `]
-}) class PortfolioPreviewDialog {
-  portfolioItem: DesignerPortfolioItem;
-  portfolioItems: DesignerPortfolioItem[];
-  currentIndex: number;
-  
-  constructor(
-    public dialogRef: MatDialogRef<PortfolioPreviewDialog>,
-    @Inject(MAT_DIALOG_DATA) public data: any
-  ) {
-    this.portfolioItem = data.portfolioItem;
-    this.portfolioItems = data.portfolioItems;
-    this.currentIndex = this.portfolioItems.findIndex(item => item.id === this.portfolioItem.id);
-  }
-  
-  formatDate(date: Date): string {
-    if (!date) return '';
-    const d = new Date(date);
-    return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
-  }
-  
-  getStars() {
-    const stars = [];
-    for (let i = 1; i <= 5; i++) {
-      stars.push(i <= this.portfolioItem.rating ? '★' : '☆');
-    }
-    return stars;
-  }
-  
-  navigate(direction: number) {
-    this.currentIndex += direction;
-    this.portfolioItem = this.portfolioItems[this.currentIndex];
-  }
-}
-
-// 生成模拟设计师数据
-const generateMockDesigner = (): Employee => {
-  return {
-    id: 'emp-design-001',
-    name: '张三设计师',
-    department: '设计部',
-    position: '高级设计师',
-    employeeId: 'EMP2020001',
-    phone: '13812345678',
-    email: 'zhang@example.com',
-    gender: '男',
-    birthDate: new Date(1990, 5, 15),
-    hireDate: new Date(2020, 3, 1),
-    status: '在职',
-    avatar: 'https://via.placeholder.com/120x120/CCCCFF/555555?text=张'
-  };
-};
-
-// 生成模拟设计师技能数据
-const generateMockSkills = (): DesignerSkill[] => {
-  const skills = [
-    { id: 'skill-001', name: '3D建模', level: 5 },
-    { id: 'skill-002', name: '渲染', level: 5 },
-    { id: 'skill-003', name: '空间设计', level: 4 },
-    { id: 'skill-004', name: '色彩搭配', level: 5 },
-    { id: 'skill-005', name: 'CAD绘图', level: 4 },
-    { id: 'skill-006', name: '客户沟通', level: 3 },
-    { id: 'skill-007', name: '项目管理', level: 3 }
-  ];
-  return skills;
-};
-
-// 生成模拟作品集数据
-const generateMockPortfolio = (): DesignerPortfolioItem[] => {
-  const portfolio: DesignerPortfolioItem[] = [];
-  const projectNames = ['现代风格客厅设计', '欧式厨房改造', '极简卧室设计', '办公室规划', '新中式书房设计', '北欧风卫生间改造', '工业风loft设计', '日式庭院景观'];
-  const descriptions = [
-    '采用简约现代风格,强调功能性与美学的平衡,通过中性色调与自然材质创造舒适空间。',
-    '融合古典欧式元素与现代厨房功能需求,营造优雅而实用的烹饪环境。',
-    '以极简主义为核心,通过留白、线条和光影创造宁静舒适的休息空间。',
-    '优化办公空间布局,提高团队协作效率,同时关注员工舒适度与健康。',
-    '结合传统中式元素与现代设计语言,打造兼具文化韵味与现代感的阅读空间。',
-    '北欧风格注重自然光线与功能性,创造明亮、干净、温馨的卫生间环境。',
-    '工业风强调原始结构与材质的暴露,创造粗犷而不失精致的loft空间。',
-    '日式庭院设计注重自然和谐,通过枯山水、绿植和石灯笼等元素营造宁静氛围。'
-  ];
-  
-  for (let i = 1; i <= 8; i++) {
-    const completionDate = new Date();
-    completionDate.setMonth(completionDate.getMonth() - i);
-    
-    portfolio.push({
-      id: `portfolio-${i}`,
-      title: projectNames[i % projectNames.length],
-      description: descriptions[i % descriptions.length],
-      imageUrl: `https://via.placeholder.com/600x400/${Math.floor(Math.random()*16777215).toString(16).padStart(6, '0')}/FFFFFF?text=作品${i}`,
-      projectId: `proj-${i}`,
-      projectName: projectNames[i % projectNames.length],
-      completionDate,
-      rating: Math.floor(Math.random() * 2) + 4 // 4-5星评分
-    });
-  }
-  
-  return portfolio;
-};
-
-// 生成模拟项目历史数据
-const generateMockProjectHistory = () => {
-  const projects = [
+})
+export class PortfolioPreviewDialog {
+  portfolioItems = [
     {
-      id: 'proj-history-001',
-      name: '现代风格客厅设计',
-      customerName: '李四',
-      startDate: new Date(2023, 1, 10),
-      endDate: new Date(2023, 3, 15),
-      status: '已完成',
-      rating: 5,
-      role: '主设计师',
-      skillsUsed: ['3D建模', '渲染', '空间设计'],
-      feedback: '设计效果非常满意,完全符合我的预期,沟通也很顺畅。'
+      imageUrl: 'assets/images/portfolio-1.svg',
+      title: '现代简约风客厅',
+      description: '为装修公司客户打造的现代简约风格客厅效果图',
+      tags: ['客厅', '现代简约', '3D建模']
     },
     {
-      id: 'proj-history-002',
-      name: '欧式厨房改造',
-      customerName: '王五',
-      startDate: new Date(2023, 4, 5),
-      endDate: new Date(2023, 6, 10),
-      status: '已完成',
-      rating: 4,
-      role: '主设计师',
-      skillsUsed: ['3D建模', '渲染', '色彩搭配'],
-      feedback: '设计很专业,细节处理到位,就是交付时间稍微延迟了一点。'
+      imageUrl: 'assets/images/portfolio-2.svg',
+      title: '北欧风卧室',
+      description: '设计工作室委托的北欧风格卧室设计',
+      tags: ['卧室', '北欧风', '软装搭配']
     },
     {
-      id: 'proj-history-003',
-      name: '极简卧室设计',
-      customerName: '赵六',
-      startDate: new Date(2023, 7, 20),
-      endDate: new Date(2023, 9, 25),
-      status: '已完成',
-      rating: 5,
-      role: '主设计师',
-      skillsUsed: ['空间设计', '色彩搭配', 'CAD绘图'],
-      feedback: '完美的极简风格,我非常喜欢,推荐给了我的朋友们。'
+      imageUrl: 'assets/images/portfolio-3.svg',
+      title: '日式风格书房',
+      description: '个人客户定制的日式风格书房设计',
+      tags: ['书房', '日式', '全屋定制']
     },
     {
-      id: 'proj-history-004',
-      name: '办公室规划',
-      customerName: '钱七',
-      startDate: new Date(2023, 10, 1),
-      endDate: null,
-      status: '进行中',
-      rating: null,
-      role: '设计顾问',
-      skillsUsed: ['空间设计', '项目管理', '客户沟通'],
-      feedback: null
+      imageUrl: 'assets/images/portfolio-4.svg',
+      title: '工业风餐厅',
+      description: '商业空间的工业风格餐厅设计',
+      tags: ['餐厅', '工业风', '商业空间']
     }
   ];
-  return projects;
-};
 
-// 生成擅长领域数据
-const generateMockSpecialties = () => {
-  return [
-    { name: '现代风格', count: 15, level: 5 },
-    { name: '极简主义', count: 12, level: 5 },
-    { name: '北欧风', count: 8, level: 4 },
-    { name: '工业风', count: 6, level: 4 },
-    { name: '新中式', count: 5, level: 3 },
-    { name: '欧式', count: 7, level: 4 }
-  ];
-};
+  constructor(private dialog: MatDialog) {}
+
+  close() {
+    this.dialog.closeAll();
+  }
+}
 
-// 主组件
 @Component({
   selector: 'app-designer-profile',
   standalone: true,
   imports: [
     CommonModule,
-    FormsModule,
-    MatButtonModule,
+    MatTabsModule,
     MatCardModule,
+    MatButtonModule,
     MatIconModule,
+    MatChipsModule,
+    MatDividerModule,
+    MatProgressBarModule,
+    MatProgressSpinnerModule,
+    MatTableModule,
+    MatBadgeModule,
     MatDialogModule,
-    MatTabsModule,
-    MatTooltipModule
+    MatTooltipModule,
+    FormsModule,
+    ReactiveFormsModule
   ],
   templateUrl: './designer-profile.html',
-  styleUrl: './designer-profile.scss'
-}) export class DesignerProfile implements OnInit {
-  // 暴露Math对象给模板使用
-  readonly Math = Math;
-  
-  // 数据
-  designer = signal<Employee>({} as Employee);
-  skills = signal<DesignerSkill[]>([]);
-  portfolio = signal<DesignerPortfolioItem[]>([]);
-  projectHistory = signal<any[]>([]);
-  specialties = signal<any[]>([]);
-  selectedPortfolioItem = signal<DesignerPortfolioItem | null>(null);
-  searchTerm = signal('');
-  selectedSkills = signal<string[]>([]);
-  activeTab = signal('overview');
-  
-  // 计算属性
-  orderedProjectHistory = computed(() => {
-    return [...this.projectHistory()].sort((a, b) => 
-      new Date(b.startDate).getTime() - new Date(a.startDate).getTime()
-    );
-  });
-  
-  inProgressProjects = computed(() => {
-    return this.projectHistory().filter(p => p.status === '进行中');
-  });
-  
-  // 获取星级评分显示
-  getStars(rating: number): string[] {
-    const stars: string[] = [];
-    const numRating = parseFloat(rating.toString()); // 确保是数字类型
-    for (let i = 1; i <= 5; i++) {
-      stars.push(i <= numRating ? '★' : '☆');
-    }
-    return stars;
-  }
-  
-  filteredPortfolio = computed(() => {
-    let filtered = this.portfolio();
-    
-    // 按技能筛选
-    if (this.selectedSkills().length > 0) {
-      // 在实际应用中,这里应该根据作品关联的技能进行筛选
-      // 这里使用随机筛选来模拟效果
-      filtered = filtered.filter(() => Math.random() > 0.3);
-    }
-    
-    // 按搜索词筛选
-    if (this.searchTerm()) {
-      const term = this.searchTerm().toLowerCase();
-      filtered = filtered.filter(item => 
-        item.title.toLowerCase().includes(term) ||
-        item.description.toLowerCase().includes(term) ||
-        item.projectName?.toLowerCase().includes(term)
-      );
-    }
-    
-    return filtered;
-  });
-  
-  // 评分统计
-  ratingStats = computed(() => {
-    const completedProjects = this.projectHistory().filter(p => p.status === '已完成');
-    const totalRating = completedProjects.reduce((sum, project) => sum + project.rating, 0);
-    const avgRating = completedProjects.length > 0 ? totalRating / completedProjects.length : 0;
-    
-    return {
-      totalProjects: completedProjects.length,
-      avgRating,
-      fiveStarCount: completedProjects.filter(p => p.rating === 5).length,
-      fourStarCount: completedProjects.filter(p => p.rating === 4).length,
-      threeStarCount: completedProjects.filter(p => p.rating === 3).length
-    };
-  });
-  
-  constructor(private dialog: MatDialog) {}
+  styleUrls: ['./designer-profile.scss']
+})
+export class DesignerProfile implements OnInit {
+  designer = signal<Designer | null>(null);
+  activeTab = signal(0);
+  projectColumns = ['name', 'clientType', 'role', 'deliveryDate', 'clientRating', 'actions'];
+  performanceColumns = ['quarter', 'customerSatisfaction', 'excellentWorkRate', 'performanceValue', 'finalGrade', 'actions'];
+  isLoading = signal(true);
+  projectsLoading = signal(true);
+  performanceLoading = signal(true);
   
+  private route = inject(ActivatedRoute);
+  private dialog = inject(MatDialog);
+
+  constructor() {}
+
   ngOnInit() {
-    // 加载模拟数据
-    this.designer.set(generateMockDesigner());
-    this.skills.set(generateMockSkills());
-    this.portfolio.set(generateMockPortfolio());
-    this.projectHistory.set(generateMockProjectHistory());
-    this.specialties.set(generateMockSpecialties());
-  }
-  
-  // 切换标签页
-  switchTab(tab: string) {
-    this.activeTab.set(tab);
+    // 从路由参数获取设计师ID
+    this.route.paramMap.subscribe(params => {
+      const id = params.get('id');
+      if (id) {
+        this.loadDesignerData(id);
+      }
+    });
+    
+    // 模拟加载项目数据
+    setTimeout(() => {
+      this.projectsLoading.set(false);
+    }, 1500);
+
+    // 模拟加载绩效数据和初始化性能趋势图表数据
+    setTimeout(() => {
+      this.performanceLoading.set(false);
+      const performanceData = this.generatePerformanceTrend();
+      console.log('Performance trend data:', performanceData);
+      // 这里可以添加图表初始化代码
+    }, 2000);
   }
-  
-  // 格式化日期
-  formatDate(date: Date | string | null): string {
-    if (!date) return '';
-    const d = new Date(date);
-    return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
+
+  loadDesignerData(id: string) {
+    // 模拟API调用加载设计师数据
+    setTimeout(() => {
+      this.designer.set({
+        id,
+        name: '张明',
+        avatar: 'assets/images/default-avatar.svg',
+        position: '高级设计师',
+        level: '高级',
+        department: '设计部-效果图组',
+        joinDate: '2020-05-15',
+        skills: ['3D建模', '渲染', '后期处理', 'CAD制图'],
+        styles: ['现代简约', '北欧风格', '新中式'],
+        availableDates: ['2023-06-15', '2023-06-16', '2023-06-17'],
+        certificates: [
+          { name: '3D效果图高级设计师认证', date: '2021-03-10', type: '技能认证' },
+          { name: '优秀设计师季度奖', date: '2022-12-20', type: '荣誉证书' }
+        ],
+        projects: [
+          {
+            id: 'P001',
+            name: '城市花园小区-89平米-现代简约',
+            clientType: '装修公司',
+            role: '效果图设计',
+            deliveryDate: '2023-05-20',
+            clientRating: 4.8,
+            tags: ['住宅', '现代简约'],
+            feedback: '设计师理解需求准确,效果图质量高,客户非常满意。'
+          },
+          {
+            id: 'P002',
+            name: '星河湾别墅-280平米-新中式',
+            clientType: '设计工作室',
+            role: '建模+渲染',
+            deliveryDate: '2023-04-15',
+            clientRating: 4.5,
+            tags: ['别墅', '新中式'],
+            feedback: '建模细节处理到位,渲染效果自然,符合预期。'
+          },
+          {
+            id: 'P003',
+            name: '商业广场-咖啡厅设计',
+            clientType: '装修公司',
+            role: '全案设计',
+            deliveryDate: '2023-03-10',
+            clientRating: 5.0,
+            tags: ['商业空间', '工业风'],
+            feedback: '从建模到后期一条龙服务,效率高,质量好,客户非常满意。'
+          }
+        ],
+        performance: [
+          {
+            quarter: '2023-Q1',
+            customerSatisfaction: 92,
+            excellentWorkRate: 85,
+            performanceValue: 88,
+            deductions: [
+              { reason: '项目延期', points: 3, date: '2023-02-15' }
+            ],
+            finalGrade: 'A'
+          },
+          {
+            quarter: '2022-Q4',
+            customerSatisfaction: 88,
+            excellentWorkRate: 80,
+            performanceValue: 85,
+            deductions: [],
+            finalGrade: 'A-'
+          },
+          {
+            quarter: '2022-Q3',
+            customerSatisfaction: 85,
+            excellentWorkRate: 78,
+            performanceValue: 82,
+            deductions: [
+              { reason: '客户投诉', points: 5, date: '2022-08-10' }
+            ],
+            finalGrade: 'B+'
+          }
+        ]
+      });
+    }, 500);
   }
-  
-  // 打开作品集预览对话框
-  openPortfolioPreview(item: DesignerPortfolioItem) {
+
+  openPortfolioPreview() {
     this.dialog.open(PortfolioPreviewDialog, {
-      width: '90vw',
+      width: '80%',
       maxWidth: '1200px',
-      maxHeight: '90vh',
-      data: {
-        portfolioItem: item,
-        portfolioItems: this.portfolio()
-      }
+      panelClass: 'portfolio-dialog-container'
     });
   }
-  
-  // 切换技能筛选
-  toggleSkillFilter(skillName: string) {
-    this.selectedSkills.update(skills => {
-      if (skills.includes(skillName)) {
-        return skills.filter(s => s !== skillName);
-      } else {
-        return [...skills, skillName];
-      }
-    });
-  }
-  
-  // 获取技能等级对应的样式
-  getSkillLevelClass(level: number): string {
-    if (level >= 5) return 'level-expert';
-    if (level >= 4) return 'level-advanced';
-    if (level >= 3) return 'level-intermediate';
-    return 'level-beginner';
+
+  getPerformanceTrend(metric: 'customerSatisfaction' | 'excellentWorkRate' | 'performanceValue'): string {
+    if (!this.designer() || this.designer()!.performance.length < 2) {
+      return 'stable';
+    }
+
+    const performance = this.designer()!.performance;
+    const current = performance[0][metric];
+    const previous = performance[1][metric];
+
+    if (current > previous + 3) return 'up';
+    if (current < previous - 3) return 'down';
+    return 'stable';
   }
-  
-  // 获取项目状态样式
-  getProjectStatusClass(status: string): string {
-    switch (status) {
-      case '已完成':
-        return 'status-completed';
-      case '进行中':
-        return 'status-in-progress';
-      default:
-        return '';
+
+  // 性能趋势分析
+  generatePerformanceTrend() {
+    // 模拟生成过去12个月的性能数据
+    const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'];
+    const data = months.map(month => ({
+      month,
+      score: Math.floor(Math.random() * 30) + 70, // 70-100之间的随机分数
+      projects: Math.floor(Math.random() * 5) + 1, // 1-5之间的随机项目数
+      revenue: Math.floor(Math.random() * 10000) + 5000 // 5000-15000之间的随机收入
+    }));
+    
+    // 确保数据有一定的趋势性,而不是完全随机
+    for (let i = 1; i < data.length; i++) {
+      // 让分数有一定的连续性
+      data[i].score = Math.max(70, Math.min(100, 
+        data[i-1].score + (Math.random() > 0.5 ? 1 : -1) * Math.floor(Math.random() * 5)));
     }
+    
+    return data;
   }
-  
 
-  
-  // 查看客户评价详情
-  viewFeedback(project: any) {
-    alert(`客户反馈: ${project.feedback}`);
+  getPerformanceColor(grade: string): string {
+    if (grade.startsWith('A')) return '#52c41a';
+    if (grade.startsWith('B')) return '#1890ff';
+    if (grade.startsWith('C')) return '#faad14';
+    return '#f5222d';
   }
-  
-  // 生成技能雷达图数据
-  getRadarChartData() {
-    // 在实际应用中,这里应该返回用于渲染雷达图的数据
-    // 这里仅返回原始技能数据,图表渲染将在前端完成
-    return this.skills().map(skill => ({
-      subject: skill.name,
-      A: skill.level,
-      fullMark: 5
-    }));
+
+  changeTab(index: number) {
+    this.activeTab.set(index);
+  }
+
+  getRatingStars(rating: number): string[] {
+    const fullStars = Math.floor(rating);
+    const halfStar = rating % 1 >= 0.5;
+    const emptyStars = 5 - fullStars - (halfStar ? 1 : 0);
+    
+    return [
+      ...Array(fullStars).fill('star'),
+      ...(halfStar ? ['star_half'] : []),
+      ...Array(emptyStars).fill('star_border')
+    ];
   }
 }

+ 229 - 1
src/app/pages/hr/employee-records/employee-records.html

@@ -1 +1,229 @@
-<p>employee-records works!</p>
+<div class="employee-records-container">
+  <!-- 页面标题和操作栏 -->
+  <div class="page-header">
+    <div class="title-section">
+      <h1>员工档案管理</h1>
+      <p class="subtitle">管理员工的基本信息、入职流程和离职流程</p>
+    </div>
+    <div class="action-buttons">
+      <button mat-raised-button color="primary" (click)="openAddEmployeeDialog()">
+        <mat-icon>add</mat-icon> 新增员工
+      </button>
+      <button mat-raised-button color="warn" [disabled]="selectedEmployees().length === 0" (click)="batchDelete()">
+        <mat-icon>delete</mat-icon> 批量删除
+      </button>
+      <button mat-button [matMenuTriggerFor]="exportMenu">
+        <mat-icon>file_download</mat-icon> 导出
+      </button>
+      <mat-menu #exportMenu="matMenu" class="hr-menu-panel">
+        <button mat-menu-item (click)="exportEmployees('员工花名册')">员工花名册</button>
+        <button mat-menu-item (click)="exportEmployees('部门人员清单')">部门人员清单</button>
+      </mat-menu>
+    </div>
+  </div>
+
+  <!-- 筛选条件 -->
+  <mat-card class="filter-card">
+    <mat-card-content>
+      <div class="filter-form">
+        <mat-form-field appearance="outline">
+          <mat-label>姓名</mat-label>
+          <input matInput [ngModel]="filterName()" (ngModelChange)="filterName.set($event)" placeholder="输入员工姓名">
+          <button *ngIf="filterName()" matSuffix mat-icon-button (click)="filterName.set('')">
+            <mat-icon>close</mat-icon>
+          </button>
+        </mat-form-field>
+
+        <mat-form-field appearance="outline">
+          <mat-label>部门</mat-label>
+          <mat-select [panelClass]="'hr-select-panel'" [ngModel]="filterDepartment()" (ngModelChange)="filterDepartment.set($event)">
+            <mat-option value="">全部</mat-option>
+            <mat-option *ngFor="let dept of departments" [value]="dept.name">{{dept.name}}</mat-option>
+          </mat-select>
+        </mat-form-field>
+
+        <mat-form-field appearance="outline">
+          <mat-label>职位</mat-label>
+          <mat-select [panelClass]="'hr-select-panel'" [ngModel]="filterPosition()" (ngModelChange)="filterPosition.set($event)">
+            <mat-option value="">全部</mat-option>
+            <mat-option *ngFor="let pos of positions" [value]="pos.name">{{pos.name}}</mat-option>
+          </mat-select>
+        </mat-form-field>
+
+        <mat-form-field appearance="outline">
+          <mat-label>状态</mat-label>
+          <mat-select [panelClass]="'hr-select-panel'" [ngModel]="filterStatus()" (ngModelChange)="filterStatus.set($event)">
+            <mat-option value="">全部</mat-option>
+            <mat-option value="在职">在职</mat-option>
+            <mat-option value="离职">离职</mat-option>
+            <mat-option value="试用期">试用期</mat-option>
+          </mat-select>
+        </mat-form-field>
+
+        <button mat-button color="primary" (click)="resetFilters()">
+          <mat-icon>refresh</mat-icon> 重置
+        </button>
+      </div>
+    </mat-card-content>
+  </mat-card>
+
+  <!-- 员工列表 -->
+  <div class="employee-table-container">
+    <table mat-table [dataSource]="filteredEmployees()" class="employee-table">
+      <!-- 选择列 -->
+      <ng-container matColumnDef="select">
+        <th mat-header-cell *matHeaderCellDef>
+          <mat-checkbox 
+            [checked]="isAllSelected()" 
+            [indeterminate]="selectedEmployees().length > 0 && !isAllSelected()"
+            (change)="toggleSelectAll($event.checked)">
+          </mat-checkbox>
+        </th>
+        <td mat-cell *matCellDef="let employee">
+          <mat-checkbox 
+            [checked]="isSelected(employee.id)"
+            (change)="toggleSelection(employee.id)">
+          </mat-checkbox>
+        </td>
+      </ng-container>
+
+      <!-- 姓名列 -->
+      <ng-container matColumnDef="name">
+        <th mat-header-cell *matHeaderCellDef>姓名</th>
+        <td mat-cell *matCellDef="let employee">{{employee.name}}</td>
+      </ng-container>
+
+      <!-- 工号列 -->
+      <ng-container matColumnDef="employeeId">
+        <th mat-header-cell *matHeaderCellDef>工号</th>
+        <td mat-cell *matCellDef="let employee">{{employee.employeeId}}</td>
+      </ng-container>
+
+      <!-- 部门列 -->
+      <ng-container matColumnDef="department">
+        <th mat-header-cell *matHeaderCellDef>部门</th>
+        <td mat-cell *matCellDef="let employee">{{employee.department}}</td>
+      </ng-container>
+
+      <!-- 职位列 -->
+      <ng-container matColumnDef="position">
+        <th mat-header-cell *matHeaderCellDef>职位</th>
+        <td mat-cell *matCellDef="let employee">{{employee.position}}</td>
+      </ng-container>
+
+      <!-- 手机号列 -->
+      <ng-container matColumnDef="phone">
+        <th mat-header-cell *matHeaderCellDef>手机号</th>
+        <td mat-cell *matCellDef="let employee">{{employee.phone}}</td>
+      </ng-container>
+
+      <!-- 入职日期列 -->
+      <ng-container matColumnDef="hireDate">
+        <th mat-header-cell *matHeaderCellDef>入职日期</th>
+        <td mat-cell *matCellDef="let employee">{{employee.hireDate | date:'yyyy-MM-dd'}}</td>
+      </ng-container>
+
+      <!-- 状态列 -->
+      <ng-container matColumnDef="status">
+        <th mat-header-cell *matHeaderCellDef>状态</th>
+        <td mat-cell *matCellDef="let employee">
+          <span class="status-badge" [ngClass]="{
+            'status-active': employee.status === '在职',
+            'status-probation': employee.status === '试用期',
+            'status-resigned': employee.status === '离职'
+          }">{{employee.status}}</span>
+        </td>
+      </ng-container>
+
+      <!-- 操作列 -->
+      <ng-container matColumnDef="actions">
+        <th mat-header-cell *matHeaderCellDef>操作</th>
+        <td mat-cell *matCellDef="let employee">
+          <button mat-icon-button [matMenuTriggerFor]="actionMenu" aria-label="操作菜单">
+            <mat-icon>more_vert</mat-icon>
+          </button>
+          <mat-menu #actionMenu="matMenu">
+            <button mat-menu-item (click)="openEditEmployeeDialog(employee)">
+              <mat-icon>edit</mat-icon>
+              <span>编辑</span>
+            </button>
+            <button mat-menu-item (click)="openOnboardingDialog(employee)" *ngIf="employee.status === '试用期'">
+              <mat-icon>assignment</mat-icon>
+              <span>入职流程</span>
+            </button>
+            <button mat-menu-item (click)="openOffboardingDialog(employee)" *ngIf="employee.status !== '离职'">
+              <mat-icon>exit_to_app</mat-icon>
+              <span>离职流程</span>
+            </button>
+            <button mat-menu-item (click)="deleteEmployee(employee)">
+              <mat-icon>delete</mat-icon>
+              <span>删除</span>
+            </button>
+          </mat-menu>
+        </td>
+      </ng-container>
+
+      <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
+      <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
+    </table>
+
+    <!-- 空状态提示 -->
+    <div *ngIf="filteredEmployees().length === 0" class="empty-state">
+      <mat-icon>person_off</mat-icon>
+      <p>没有找到符合条件的员工记录</p>
+      <button mat-raised-button color="primary" (click)="resetFilters()">重置筛选条件</button>
+    </div>
+  </div>
+
+  <!-- 分页器 -->
+  <mat-paginator [length]="filteredEmployees().length"
+                [pageSize]="10"
+                [pageSizeOptions]="[5, 10, 25, 50]"
+                showFirstLastButtons>
+  </mat-paginator>
+
+  <!-- 快捷功能卡片 -->
+  <div class="quick-actions">
+    <mat-card class="quick-action-card">
+      <mat-card-header>
+        <mat-icon mat-card-avatar>assignment_ind</mat-icon>
+        <mat-card-title>入职流程</mat-card-title>
+        <mat-card-subtitle>新员工入职流程管理</mat-card-subtitle>
+      </mat-card-header>
+      <mat-card-content>
+        <p>待处理入职流程: <span class="highlight-count">2</span></p>
+      </mat-card-content>
+      <mat-card-actions>
+        <button mat-button color="primary" (click)="openQuickAction('onboarding')">查看详情</button>
+      </mat-card-actions>
+    </mat-card>
+
+    <mat-card class="quick-action-card">
+      <mat-card-header>
+        <mat-icon mat-card-avatar>exit_to_app</mat-icon>
+        <mat-card-title>离职流程</mat-card-title>
+        <mat-card-subtitle>员工离职流程管理</mat-card-subtitle>
+      </mat-card-header>
+      <mat-card-content>
+        <p>待处理离职流程: <span class="highlight-count">1</span></p>
+      </mat-card-content>
+      <mat-card-actions>
+        <button mat-button color="primary" (click)="openQuickAction('offboarding')">查看详情</button>
+      </mat-card-actions>
+    </mat-card>
+
+    <mat-card class="quick-action-card">
+      <mat-card-header>
+        <mat-icon mat-card-avatar>warning</mat-icon>
+        <mat-card-title>试用期提醒</mat-card-title>
+        <mat-card-subtitle>试用期即将到期员工</mat-card-subtitle>
+      </mat-card-header>
+      <mat-card-content>
+        <p>即将到期员工: <span class="highlight-count">3</span></p>
+      </mat-card-content>
+      <mat-card-actions>
+        <button mat-button color="primary" (click)="openQuickAction('probation')">查看详情</button>
+      </mat-card-actions>
+    </mat-card>
+  </div>
+</div>

+ 247 - 0
src/app/pages/hr/employee-records/employee-records.scss

@@ -0,0 +1,247 @@
+@import '../../../shared/styles/variables';
+
+.employee-records-container {
+  padding: 24px;
+  
+  .page-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 24px;
+    
+    .title-section {
+      h1 {
+        margin: 0;
+        font-size: 24px;
+        font-weight: 500;
+        color: #1a3a6e;
+      }
+      
+      .subtitle {
+        margin: 4px 0 0;
+        color: #666;
+        font-size: 14px;
+      }
+    }
+    
+    .action-buttons {
+      display: flex;
+      gap: 12px;
+
+      // 强化按钮对比度
+      button[mat-raised-button][color='warn'] {
+        background: #ff4d4f;
+        color: #fff;
+      }
+      button[mat-raised-button][color='primary'] {
+        background: linear-gradient(90deg, #165DFF 0%, #7c3aed 100%);
+        color: #fff;
+      }
+    }
+  }
+  
+  .filter-card {
+    margin-bottom: 24px;
+    backdrop-filter: saturate(180%) blur(8px);
+    border-radius: 12px;
+    box-shadow: 0 6px 14px rgba(22,93,255,0.08);
+
+    .filter-form {
+      display: flex;
+      flex-wrap: wrap;
+      gap: 16px;
+      align-items: center;
+      
+      mat-form-field {
+        flex: 1;
+        min-width: 200px;
+      }
+    }
+  }
+  
+  .employee-table-container {
+    background-color: #fff;
+    border-radius: 12px;
+    box-shadow: 0 10px 24px rgba(0, 0, 0, 0.06);
+    margin-bottom: 24px;
+    overflow: auto;
+    
+    .employee-table {
+      width: 100%;
+      
+      th.mat-header-cell {
+        background: linear-gradient(180deg, #f7f9fc 0%, #eef3ff 100%);
+        color: #1a3a6e;
+        font-weight: 600;
+        padding: 12px 16px;
+      }
+      
+      td.mat-cell {
+        padding: 12px 16px;
+      }
+      
+      .status-badge {
+        display: inline-block;
+        padding: 4px 8px;
+        border-radius: 12px;
+        font-size: 12px;
+        font-weight: 500;
+        text-align: center;
+        min-width: 60px;
+        
+        &.status-active {
+          background-color: #e6f7ee;
+          color: #00a854;
+        }
+        
+        &.status-probation {
+          background-color: #fff7e6;
+          color: #fa8c16;
+        }
+        
+        &.status-resigned {
+          background-color: #f5f5f5;
+          color: #999;
+        }
+      }
+    }
+    
+    .empty-state {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      padding: 48px 24px;
+      text-align: center;
+      
+      mat-icon {
+        font-size: 48px;
+        height: 48px;
+        width: 48px;
+        color: #ccc;
+        margin-bottom: 16px;
+      }
+      
+      p {
+        color: #666;
+        margin-bottom: 16px;
+      }
+    }
+  }
+  
+  mat-paginator {
+    margin-bottom: 24px;
+  }
+  
+  .quick-actions {
+    display: grid;
+    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
+    gap: 24px;
+    margin-bottom: 24px;
+    
+    .quick-action-card {
+      border-radius: 12px;
+      transition: transform 0.2s, box-shadow 0.2s;
+      box-shadow: 0 6px 16px rgba(0,0,0,0.06);
+      
+      &:hover {
+        transform: translateY(-4px);
+        box-shadow: 0 12px 24px rgba(0, 0, 0, 0.12);
+      }
+      
+      mat-card-header {
+        margin-bottom: 16px;
+        
+        mat-icon {
+          background: linear-gradient(135deg, #e6f7ff 0%, #f3e8ff 100%);
+          color: #1a3a6e;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          border-radius: 50%;
+        }
+      }
+      
+      mat-card-content {
+        .highlight-count {
+          font-weight: 600;
+          color: #1a3a6e;
+          font-size: 18px;
+        }
+      }
+      
+      mat-card-actions {
+        padding: 8px 16px 16px;
+      }
+    }
+  }
+
+  // Material 选择面板样式
+  :host ::ng-deep .hr-select-panel {
+    background: #fff;
+    border-radius: 10px !important;
+    box-shadow: 0 12px 30px rgba(22,93,255,0.15);
+
+    .mat-mdc-option.mdc-list-item {
+      padding: 10px 16px;
+    }
+    .mdc-list-item__primary-text {
+      color: #1a3a6e;
+    }
+    .mat-mdc-option.mdc-list-item--selected .mdc-list-item__primary-text {
+      color: #165DFF;
+      font-weight: 600;
+    }
+  }
+
+  // Material 菜单面板样式
+  :host ::ng-deep .hr-menu-panel {
+    background: #fff;
+    border-radius: 10px !important;
+    box-shadow: 0 12px 30px rgba(22,93,255,0.15);
+  }
+  
+  // HR 对话框统一样式
+  :host ::ng-deep .hr-dialog {
+    .mat-mdc-dialog-container .mdc-dialog__surface {
+      border-radius: 12px;
+      background: #ffffff;
+      box-shadow: 0 12px 30px rgba(22, 93, 255, 0.15);
+    }
+
+    .mat-mdc-dialog-title {
+      color: #1a3a6e;
+      font-weight: 600;
+    }
+
+    .mat-mdc-dialog-actions {
+      padding: 8px 24px 20px;
+
+      .mat-mdc-raised-button {
+        background: linear-gradient(90deg, #165DFF 0%, #7c3aed 100%);
+        color: #fff;
+      }
+    }
+  }
+}
+
+// 响应式布局调整
+@media (max-width: 768px) {
+  .employee-records-container {
+    .page-header {
+      flex-direction: column;
+      align-items: flex-start;
+      gap: 16px;
+      
+      .action-buttons {
+        width: 100%;
+        justify-content: flex-start;
+        flex-wrap: wrap;
+      }
+    }
+    
+    .quick-actions {
+      grid-template-columns: 1fr;
+    }
+  }
+}

+ 798 - 4
src/app/pages/hr/employee-records/employee-records.ts

@@ -1,11 +1,805 @@
-import { Component } from '@angular/core';
+import { Component, OnInit, signal, computed, Inject } from '@angular/core';
+import { CommonModule, NgIf, NgFor } from '@angular/common';
+import { FormsModule, ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { MatButtonModule } from '@angular/material/button';
+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 { MatDialogModule, MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
+import { MatTableModule } from '@angular/material/table';
+import { MatPaginatorModule } from '@angular/material/paginator';
+import { MatCheckboxModule } from '@angular/material/checkbox';
+import { MatSnackBarModule, MatSnackBar } from '@angular/material/snack-bar';
+import { MatIconModule } from '@angular/material/icon';
+import { MatTooltipModule } from '@angular/material/tooltip';
+import { MatTabsModule } from '@angular/material/tabs';
+import { MatChipsModule } from '@angular/material/chips';
+import { MatMenuModule } from '@angular/material/menu';
+import { MatCardModule } from '@angular/material/card';
+import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
+import { Employee, Department, Position, Contract, Certificate, EmployeeStatus } from '../../../models/hr.model';
+
+// 新增员工对话框组件
+@Component({
+  selector: 'app-add-employee-dialog',
+  standalone: true,
+  imports: [
+    CommonModule,
+    NgFor,
+    FormsModule,
+    ReactiveFormsModule,
+    MatButtonModule,
+    MatInputModule,
+    MatSelectModule,
+    MatDatepickerModule,
+    MatNativeDateModule,
+    MatDialogModule,
+    MatIconModule
+  ],
+  template: `
+    <h2 mat-dialog-title>{{data.isEdit ? '编辑员工信息' : '新增员工'}}</h2>
+    <mat-dialog-content>
+      <form [formGroup]="employeeForm" class="employee-form">
+        <div class="form-row">
+          <mat-form-field appearance="outline">
+            <mat-label>姓名</mat-label>
+            <input matInput formControlName="name" required>
+            <mat-error *ngIf="employeeForm.get('name')?.hasError('required')">姓名不能为空</mat-error>
+          </mat-form-field>
+          
+          <mat-form-field appearance="outline">
+            <mat-label>工号</mat-label>
+            <input matInput formControlName="employeeId" required>
+            <mat-error *ngIf="employeeForm.get('employeeId')?.hasError('required')">工号不能为空</mat-error>
+          </mat-form-field>
+        </div>
+        
+        <div class="form-row">
+          <mat-form-field appearance="outline">
+            <mat-label>部门</mat-label>
+            <mat-select formControlName="department" required>
+              <mat-option *ngFor="let dept of departments" [value]="dept.name">{{dept.name}}</mat-option>
+            </mat-select>
+            <mat-error *ngIf="employeeForm.get('department')?.hasError('required')">请选择部门</mat-error>
+          </mat-form-field>
+          
+          <mat-form-field appearance="outline">
+            <mat-label>职位</mat-label>
+            <mat-select formControlName="position" required>
+              <mat-option *ngFor="let pos of positions" [value]="pos.name">{{pos.name}}</mat-option>
+            </mat-select>
+            <mat-error *ngIf="employeeForm.get('position')?.hasError('required')">请选择职位</mat-error>
+          </mat-form-field>
+        </div>
+        
+        <div class="form-row">
+          <mat-form-field appearance="outline">
+            <mat-label>手机号码</mat-label>
+            <input matInput formControlName="phone" required>
+            <mat-error *ngIf="employeeForm.get('phone')?.hasError('required')">手机号码不能为空</mat-error>
+            <mat-error *ngIf="employeeForm.get('phone')?.hasError('pattern')">请输入有效的手机号码</mat-error>
+          </mat-form-field>
+          
+          <mat-form-field appearance="outline">
+            <mat-label>邮箱</mat-label>
+            <input matInput formControlName="email" required>
+            <mat-error *ngIf="employeeForm.get('email')?.hasError('required')">邮箱不能为空</mat-error>
+            <mat-error *ngIf="employeeForm.get('email')?.hasError('email')">请输入有效的邮箱地址</mat-error>
+          </mat-form-field>
+        </div>
+        
+        <div class="form-row">
+          <mat-form-field appearance="outline">
+            <mat-label>性别</mat-label>
+            <mat-select formControlName="gender" required>
+              <mat-option value="男">男</mat-option>
+              <mat-option value="女">女</mat-option>
+            </mat-select>
+            <mat-error *ngIf="employeeForm.get('gender')?.hasError('required')">请选择性别</mat-error>
+          </mat-form-field>
+          
+          <mat-form-field appearance="outline">
+            <mat-label>出生日期</mat-label>
+            <input matInput [matDatepicker]="birthDatePicker" formControlName="birthDate">
+            <mat-datepicker-toggle matSuffix [for]="birthDatePicker"></mat-datepicker-toggle>
+            <mat-datepicker #birthDatePicker></mat-datepicker>
+          </mat-form-field>
+        </div>
+        
+        <div class="form-row">
+          <mat-form-field appearance="outline">
+            <mat-label>入职日期</mat-label>
+            <input matInput [matDatepicker]="hireDatePicker" formControlName="hireDate" required>
+            <mat-datepicker-toggle matSuffix [for]="hireDatePicker"></mat-datepicker-toggle>
+            <mat-datepicker #hireDatePicker></mat-datepicker>
+            <mat-error *ngIf="employeeForm.get('hireDate')?.hasError('required')">入职日期不能为空</mat-error>
+          </mat-form-field>
+          
+          <mat-form-field appearance="outline">
+            <mat-label>员工状态</mat-label>
+            <mat-select formControlName="status" required>
+              <mat-option value="在职">在职</mat-option>
+              <mat-option value="离职">离职</mat-option>
+              <mat-option value="试用期">试用期</mat-option>
+            </mat-select>
+            <mat-error *ngIf="employeeForm.get('status')?.hasError('required')">请选择员工状态</mat-error>
+          </mat-form-field>
+        </div>
+      </form>
+    </mat-dialog-content>
+    <mat-dialog-actions align="end">
+      <button mat-button mat-dialog-close>取消</button>
+      <button mat-raised-button color="primary" [disabled]="employeeForm.invalid" (click)="saveEmployee()">保存</button>
+    </mat-dialog-actions>
+  `,
+  styles: [`
+    .employee-form {
+      display: flex;
+      flex-direction: column;
+      gap: 16px;
+      padding: 8px;
+    }
+    
+    .form-row {
+      display: flex;
+      gap: 16px;
+      
+      mat-form-field {
+        flex: 1;
+      }
+    }
+  `]
+})
+export class AddEmployeeDialog implements OnInit {
+  employeeForm: FormGroup;
+  departments: Department[] = [
+    { id: '1', name: '设计部', employeeCount: 0 },
+    { id: '2', name: '客服部', employeeCount: 0 },
+    { id: '3', name: '技术部', employeeCount: 0 },
+    { id: '4', name: '行政部', employeeCount: 0 }
+  ];
+  
+  positions: Position[] = [
+    { id: '1', name: '设计师', departmentId: '1', departmentName: '设计部', level: '中级' },
+    { id: '2', name: '客服专员', departmentId: '2', departmentName: '客服部', level: '初级' },
+    { id: '3', name: '技术组长', departmentId: '3', departmentName: '技术部', level: '高级' },
+    { id: '4', name: '前端开发', departmentId: '3', departmentName: '技术部', level: '中级' },
+    { id: '5', name: '行政专员', departmentId: '4', departmentName: '行政部', level: '初级' }
+  ];
+  
+  constructor(
+    private fb: FormBuilder,
+    public dialogRef: MatDialogRef<AddEmployeeDialog>,
+    @Inject(MAT_DIALOG_DATA) public data: {employee?: Employee, isEdit: boolean}
+  ) {
+    this.employeeForm = this.fb.group({
+      name: ['', Validators.required],
+      employeeId: ['', Validators.required],
+      department: ['', Validators.required],
+      position: ['', Validators.required],
+      phone: ['', [Validators.required, Validators.pattern(/^1[3-9]\d{9}$/)]],
+      email: ['', [Validators.required, Validators.email]],
+      gender: ['', Validators.required],
+      birthDate: [null],
+      hireDate: [new Date(), Validators.required],
+      status: ['试用期', Validators.required]
+    });
+    
+    if (data.isEdit && data.employee) {
+      this.employeeForm.patchValue(data.employee);
+    }
+  }
+  
+  ngOnInit() {}
+  
+  saveEmployee() {
+    if (this.employeeForm.valid) {
+      const employeeData = this.employeeForm.value;
+      this.dialogRef.close(employeeData);
+    }
+  }
+}
+
+// 入职流程对话框组件
+@Component({
+  selector: 'app-onboarding-dialog',
+  standalone: true,
+  imports: [
+    CommonModule,
+    NgFor,
+    FormsModule,
+    ReactiveFormsModule,
+    MatButtonModule,
+    MatInputModule,
+    MatSelectModule,
+    MatDatepickerModule,
+    MatNativeDateModule,
+    MatDialogModule,
+    MatIconModule,
+    MatCheckboxModule
+  ],
+  template: `
+    <h2 mat-dialog-title>入职流程</h2>
+    <mat-dialog-content>
+      <div class="employee-info">
+        <h3>员工信息</h3>
+        <p><strong>姓名:</strong> {{data.employee.name}}</p>
+        <p><strong>部门:</strong> {{data.employee.department}}</p>
+        <p><strong>职位:</strong> {{data.employee.position}}</p>
+        <p><strong>入职日期:</strong> {{data.employee.hireDate | date:'yyyy-MM-dd'}}</p>
+      </div>
+      
+      <div class="onboarding-tasks">
+        <h3>入职任务清单</h3>
+        <div class="task-list">
+          <div *ngFor="let task of onboardingTasks" class="task-item">
+            <mat-checkbox [(ngModel)]="task.completed">{{task.name}}</mat-checkbox>
+            <span class="task-assignee">负责人: {{task.assignee}}</span>
+          </div>
+        </div>
+      </div>
+    </mat-dialog-content>
+    <mat-dialog-actions align="end">
+      <button mat-button mat-dialog-close>关闭</button>
+      <button mat-raised-button color="primary" (click)="saveOnboarding()">保存进度</button>
+    </mat-dialog-actions>
+  `,
+  styles: [`
+    .employee-info {
+      margin-bottom: 24px;
+      padding: 16px;
+      background-color: #f5f5f5;
+      border-radius: 4px;
+      
+      h3 {
+        margin-top: 0;
+        margin-bottom: 16px;
+        color: #1a3a6e;
+      }
+      
+      p {
+        margin: 8px 0;
+      }
+    }
+    
+    .onboarding-tasks {
+      h3 {
+        margin-bottom: 16px;
+        color: #1a3a6e;
+      }
+    }
+    
+    .task-list {
+      display: flex;
+      flex-direction: column;
+      gap: 12px;
+    }
+    
+    .task-item {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      padding: 8px 0;
+      border-bottom: 1px solid #e0e0e0;
+      
+      &:last-child {
+        border-bottom: none;
+      }
+      
+      .task-assignee {
+        color: #757575;
+        font-size: 12px;
+      }
+    }
+  `]
+})
+export class OnboardingDialog {
+  onboardingTasks = [
+    { name: '签署劳动合同', completed: false, assignee: '人事' },
+    { name: '录入系统权限', completed: false, assignee: '技术支持' },
+    { name: '领取办公用品', completed: false, assignee: '行政' },
+    { name: '参加新员工培训', completed: false, assignee: '培训部' },
+    { name: '技术组长审核岗位匹配度', completed: false, assignee: '技术组长' }
+  ];
+  
+  constructor(
+    public dialogRef: MatDialogRef<OnboardingDialog>,
+    @Inject(MAT_DIALOG_DATA) public data: {employee: Employee}
+  ) {}
+  
+  saveOnboarding() {
+    this.dialogRef.close(this.onboardingTasks);
+  }
+}
+
+// 离职流程对话框组件
+@Component({
+  selector: 'app-offboarding-dialog',
+  standalone: true,
+  imports: [
+    CommonModule,
+    NgFor,
+    FormsModule,
+    ReactiveFormsModule,
+    MatButtonModule,
+    MatInputModule,
+    MatSelectModule,
+    MatDatepickerModule,
+    MatNativeDateModule,
+    MatDialogModule,
+    MatIconModule,
+    MatCheckboxModule
+  ],
+  template: `
+    <h2 mat-dialog-title>离职流程</h2>
+    <mat-dialog-content>
+      <form [formGroup]="offboardingForm" class="offboarding-form">
+        <div class="employee-info">
+          <h3>员工信息</h3>
+          <p><strong>姓名:</strong> {{data.employee.name}}</p>
+          <p><strong>部门:</strong> {{data.employee.department}}</p>
+          <p><strong>职位:</strong> {{data.employee.position}}</p>
+          <p><strong>入职日期:</strong> {{data.employee.hireDate | date:'yyyy-MM-dd'}}</p>
+        </div>
+        
+        <div class="offboarding-details">
+          <h3>离职信息</h3>
+          
+          <mat-form-field appearance="outline" class="full-width">
+            <mat-label>离职原因</mat-label>
+            <mat-select formControlName="reason" required>
+              <mat-option value="个人发展">个人发展</mat-option>
+              <mat-option value="薪资问题">薪资问题</mat-option>
+              <mat-option value="工作环境">工作环境</mat-option>
+              <mat-option value="家庭原因">家庭原因</mat-option>
+              <mat-option value="健康原因">健康原因</mat-option>
+              <mat-option value="其他">其他</mat-option>
+            </mat-select>
+            <mat-error *ngIf="offboardingForm.get('reason')?.hasError('required')">请选择离职原因</mat-error>
+          </mat-form-field>
+          
+          <mat-form-field appearance="outline" class="full-width">
+            <mat-label>离职日期</mat-label>
+            <input matInput [matDatepicker]="resignDatePicker" formControlName="resignDate" required>
+            <mat-datepicker-toggle matSuffix [for]="resignDatePicker"></mat-datepicker-toggle>
+            <mat-datepicker #resignDatePicker></mat-datepicker>
+            <mat-error *ngIf="offboardingForm.get('resignDate')?.hasError('required')">离职日期不能为空</mat-error>
+          </mat-form-field>
+          
+          <mat-form-field appearance="outline" class="full-width">
+            <mat-label>工作交接要求</mat-label>
+            <textarea matInput formControlName="handoverRequirements" rows="3"></textarea>
+          </mat-form-field>
+        </div>
+        
+        <div class="offboarding-tasks">
+          <h3>离职任务清单</h3>
+          <div class="task-list">
+            <div *ngFor="let task of offboardingTasks" class="task-item">
+              <mat-checkbox [(ngModel)]="task.completed" [ngModelOptions]="{standalone: true}">
+                {{task.name}}
+              </mat-checkbox>
+              <span class="task-assignee">负责人: {{task.assignee}}</span>
+            </div>
+          </div>
+        </div>
+      </form>
+    </mat-dialog-content>
+    <mat-dialog-actions align="end">
+      <button mat-button mat-dialog-close>取消</button>
+      <button mat-raised-button color="primary" [disabled]="offboardingForm.invalid" (click)="saveOffboarding()">提交</button>
+    </mat-dialog-actions>
+  `,
+  styles: [`
+    .offboarding-form {
+      display: flex;
+      flex-direction: column;
+      gap: 24px;
+    }
+    
+    .employee-info {
+      padding: 16px;
+      background-color: #f5f5f5;
+      border-radius: 4px;
+      
+      h3 {
+        margin-top: 0;
+        margin-bottom: 16px;
+        color: #1a3a6e;
+      }
+      
+      p {
+        margin: 8px 0;
+      }
+    }
+    
+    .offboarding-details,
+    .offboarding-tasks {
+      h3 {
+        margin-bottom: 16px;
+        color: #1a3a6e;
+      }
+    }
+    
+    .full-width {
+      width: 100%;
+    }
+    
+    .task-list {
+      display: flex;
+      flex-direction: column;
+      gap: 12px;
+    }
+    
+    .task-item {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      padding: 8px 0;
+      border-bottom: 1px solid #e0e0e0;
+      
+      &:last-child {
+        border-bottom: none;
+      }
+      
+      .task-assignee {
+        color: #757575;
+        font-size: 12px;
+      }
+    }
+  `]
+})
+export class OffboardingDialog implements OnInit {
+  offboardingForm: FormGroup;
+  offboardingTasks = [
+    { name: '项目交接', completed: false, assignee: '技术组长' },
+    { name: '客户信息同步', completed: false, assignee: '客服主管' },
+    { name: '薪资结算', completed: false, assignee: '财务' },
+    { name: '归还办公设备', completed: false, assignee: '行政' },
+    { name: '系统权限冻结', completed: false, assignee: '技术支持' }
+  ];
+  
+  constructor(
+    private fb: FormBuilder,
+    public dialogRef: MatDialogRef<OffboardingDialog>,
+    @Inject(MAT_DIALOG_DATA) public data: {employee: Employee}
+  ) {
+    this.offboardingForm = this.fb.group({
+      reason: ['', Validators.required],
+      resignDate: [new Date(), Validators.required],
+      handoverRequirements: ['']
+    });
+  }
+  
+  ngOnInit() {}
+  
+  saveOffboarding() {
+    if (this.offboardingForm.valid) {
+      const offboardingData = {
+        ...this.offboardingForm.value,
+        tasks: this.offboardingTasks
+      };
+      this.dialogRef.close(offboardingData);
+    }
+  }
+}
 
 @Component({
   selector: 'app-employee-records',
-  imports: [],
+  standalone: true,
+  imports: [
+    CommonModule,
+    NgIf,
+    NgFor,
+    FormsModule,
+    ReactiveFormsModule,
+    MatButtonModule,
+    MatInputModule,
+    MatSelectModule,
+    MatDatepickerModule,
+    MatNativeDateModule,
+    MatDialogModule,
+    MatTableModule,
+    MatPaginatorModule,
+    MatCheckboxModule,
+    MatSnackBarModule,
+    MatIconModule,
+    MatTooltipModule,
+    MatTabsModule,
+    MatChipsModule,
+    MatMenuModule,
+    MatCardModule,
+    MatProgressSpinnerModule
+  ],
   templateUrl: './employee-records.html',
-  styleUrl: './employee-records.scss'
+  styleUrls: ['./employee-records.scss']
 })
-export class EmployeeRecords {
+export class EmployeeRecords implements OnInit {
+  // 员工数据
+  employees = signal<Employee[]>([
+    {
+      id: '1',
+      name: '张三',
+      department: '设计部',
+      position: '设计师',
+      employeeId: 'DS001',
+      phone: '13800138001',
+      email: 'zhangsan@example.com',
+      gender: '男',
+      birthDate: new Date('1990-01-01'),
+      hireDate: new Date('2022-01-15'),
+      status: '在职'
+    },
+    {
+      id: '2',
+      name: '李四',
+      department: '客服部',
+      position: '客服专员',
+      employeeId: 'CS001',
+      phone: '13800138002',
+      email: 'lisi@example.com',
+      gender: '女',
+      birthDate: new Date('1992-05-20'),
+      hireDate: new Date('2022-03-10'),
+      status: '在职'
+    },
+    {
+      id: '3',
+      name: '王五',
+      department: '技术部',
+      position: '前端开发',
+      employeeId: 'FE001',
+      phone: '13800138003',
+      email: 'wangwu@example.com',
+      gender: '男',
+      birthDate: new Date('1988-11-15'),
+      hireDate: new Date('2023-01-05'),
+      status: '试用期'
+    },
+    {
+      id: '4',
+      name: '赵六',
+      department: '设计部',
+      position: '高级设计师',
+      employeeId: 'DS002',
+      phone: '13800138004',
+      email: 'zhaoliu@example.com',
+      gender: '男',
+      birthDate: new Date('1985-07-22'),
+      hireDate: new Date('2021-06-18'),
+      status: '在职'
+    },
+    {
+      id: '5',
+      name: '钱七',
+      department: '技术部',
+      position: '技术组长',
+      employeeId: 'TL001',
+      phone: '13800138005',
+      email: 'qianqi@example.com',
+      gender: '男',
+      birthDate: new Date('1983-03-30'),
+      hireDate: new Date('2020-09-01'),
+      status: '在职'
+    }
+  ]);
+  
+  // 筛选条件
+  filterName = signal('');
+  filterDepartment = signal('');
+  filterPosition = signal('');
+  filterStatus = signal('');
+  
+  // 表格列定义
+  displayedColumns = ['select', 'name', 'employeeId', 'department', 'position', 'phone', 'hireDate', 'status', 'actions'];
+  
+  // 选中的员工
+  selectedEmployees = signal<string[]>([]);
+  
+  // 部门和职位数据
+  departments = [
+    { id: '1', name: '设计部', employeeCount: 25 },
+    { id: '2', name: '客服部', employeeCount: 18 },
+    { id: '3', name: '技术部', employeeCount: 30 },
+    { id: '4', name: '行政部', employeeCount: 10 }
+  ];
+  
+  positions = [
+    { id: '1', name: '设计师', departmentId: '1', departmentName: '设计部', level: '中级' },
+    { id: '2', name: '高级设计师', departmentId: '1', departmentName: '设计部', level: '高级' },
+    { id: '3', name: '客服专员', departmentId: '2', departmentName: '客服部', level: '初级' },
+    { id: '4', name: '客服主管', departmentId: '2', departmentName: '客服部', level: '高级' },
+    { id: '5', name: '前端开发', departmentId: '3', departmentName: '技术部', level: '中级' },
+    { id: '6', name: '技术组长', departmentId: '3', departmentName: '技术部', level: '高级' },
+    { id: '7', name: '行政专员', departmentId: '4', departmentName: '行政部', level: '初级' }
+  ];
+  
+  // 计算过滤后的员工列表
+  filteredEmployees = computed(() => {
+    return this.employees().filter(employee => {
+      const nameMatch = this.filterName() === '' || employee.name.includes(this.filterName());
+      const departmentMatch = this.filterDepartment() === '' || employee.department === this.filterDepartment();
+      const positionMatch = this.filterPosition() === '' || employee.position === this.filterPosition();
+      const statusMatch = this.filterStatus() === '' || employee.status === this.filterStatus();
+      
+      return nameMatch && departmentMatch && positionMatch && statusMatch;
+    });
+  });
+  
+  constructor(
+    private dialog: MatDialog,
+    private snackBar: MatSnackBar
+  ) {}
+  
+  ngOnInit() {}
+  
+  // 打开新增员工对话框
+  openAddEmployeeDialog() {
+    const dialogRef = this.dialog.open(AddEmployeeDialog, {
+      width: '700px',
+      panelClass: 'hr-dialog',
+      data: { isEdit: false }
+    });
+    
+    dialogRef.afterClosed().subscribe(result => {
+      if (result) {
+        const newEmployee: Employee = {
+          id: (this.employees().length + 1).toString(),
+          ...result
+        };
+        
+        this.employees.update(employees => [...employees, newEmployee]);
+        this.showSnackBar('员工添加成功');
+      }
+    });
+  }
+  
+  // 打开编辑员工对话框
+  openEditEmployeeDialog(employee: Employee) {
+    const dialogRef = this.dialog.open(AddEmployeeDialog, {
+      width: '700px',
+      panelClass: 'hr-dialog',
+      data: { employee, isEdit: true }
+    });
+    
+    dialogRef.afterClosed().subscribe(result => {
+      if (result) {
+        this.employees.update(employees => 
+          employees.map(emp => emp.id === employee.id ? { ...emp, ...result } : emp)
+        );
+        this.showSnackBar('员工信息更新成功');
+      }
+    });
+  }
+  
+  // 打开入职流程对话框
+  openOnboardingDialog(employee: Employee) {
+    const dialogRef = this.dialog.open(OnboardingDialog, {
+      width: '600px',
+      panelClass: 'hr-dialog',
+      data: { employee }
+    });
+    
+    dialogRef.afterClosed().subscribe(result => {
+      if (result) {
+        this.showSnackBar('入职流程更新成功');
+      }
+    });
+  }
+  
+  // 打开离职流程对话框
+  openOffboardingDialog(employee: Employee) {
+    const dialogRef = this.dialog.open(OffboardingDialog, {
+      width: '600px',
+      panelClass: 'hr-dialog',
+      data: { employee }
+    });
+    
+    dialogRef.afterClosed().subscribe(result => {
+      if (result) {
+        // 更新员工状态为离职
+        this.employees.update(employees => 
+          employees.map(emp => emp.id === employee.id ? { ...emp, status: '离职' } : emp)
+        );
+        this.showSnackBar('离职流程已启动');
+      }
+    });
+  }
+  
+  // 删除员工
+  deleteEmployee(employee: Employee) {
+    if (confirm(`确定要删除员工 ${employee.name} 吗?`)) {
+      this.employees.update(employees => 
+        employees.filter(emp => emp.id !== employee.id)
+      );
+      this.showSnackBar('员工已删除');
+    }
+  }
+  
+  // 批量删除员工
+  batchDelete() {
+    if (this.selectedEmployees().length === 0) {
+      this.showSnackBar('请先选择要删除的员工');
+      return;
+    }
+    
+    if (confirm(`确定要删除选中的 ${this.selectedEmployees().length} 名员工吗?`)) {
+      this.employees.update(employees => 
+        employees.filter(emp => !this.selectedEmployees().includes(emp.id))
+      );
+      this.selectedEmployees.set([]);
+      this.showSnackBar('批量删除成功');
+    }
+  }
+  
+  // 导出员工数据
+  exportEmployees(type: string) {
+    // 实际项目中这里会调用导出服务
+    this.showSnackBar(`已导出${type}`);
+  }
 
+  // 快捷卡片查看详情
+  openQuickAction(type: 'onboarding' | 'offboarding' | 'probation') {
+    switch (type) {
+      case 'onboarding':
+        // 这里可以导航或弹窗,先给出提示
+        this.showSnackBar('前往入职流程列表');
+        break;
+      case 'offboarding':
+        this.showSnackBar('前往离职流程列表');
+        break;
+      case 'probation':
+        this.showSnackBar('查看试用期即将到期员工');
+        break;
+    }
+  }
+  
+  // 选择/取消选择单个员工
+  toggleSelection(employeeId: string) {
+    if (this.isSelected(employeeId)) {
+      this.selectedEmployees.update(selected => selected.filter(id => id !== employeeId));
+    } else {
+      this.selectedEmployees.update(selected => [...selected, employeeId]);
+    }
+  }
+  
+  // 选择/取消选择所有员工
+  toggleSelectAll(checked: boolean) {
+    if (checked) {
+      const allIds = this.filteredEmployees().map(emp => emp.id);
+      this.selectedEmployees.set(allIds);
+    } else {
+      this.selectedEmployees.set([]);
+    }
+  }
+  
+  // 检查员工是否被选中
+  isSelected(employeeId: string): boolean {
+    return this.selectedEmployees().includes(employeeId);
+  }
+  
+  // 检查是否所有员工都被选中
+  isAllSelected(): boolean {
+    return this.filteredEmployees().length > 0 && 
+           this.filteredEmployees().every(emp => this.selectedEmployees().includes(emp.id));
+  }
+  
+  // 显示提示消息
+  showSnackBar(message: string) {
+    this.snackBar.open(message, '关闭', {
+      duration: 3000,
+      horizontalPosition: 'center',
+      verticalPosition: 'bottom',
+      panelClass: ['success-snackbar']
+    });
+  }
+  
+  // 重置筛选条件
+  resetFilters() {
+    this.filterName.set('');
+    this.filterDepartment.set('');
+    this.filterPosition.set('');
+    this.filterStatus.set('');
+  }
 }

+ 60 - 0
src/app/pages/hr/hr-layout/hr-layout.html

@@ -0,0 +1,60 @@
+<div class="hr-layout-container">
+  <!-- 侧边导航栏 -->
+  <mat-sidenav-container class="sidenav-container">
+    <mat-sidenav #sidenav mode="side" [opened]="true" class="sidenav" [class.expanded]="isExpanded" [class.collapsed]="!isExpanded">
+      <!-- 导航栏头部 -->
+      <div class="sidenav-header">
+        <div class="logo-container">
+          <img *ngIf="isExpanded" src="assets/images/hr-logo.svg" alt="HR Logo" class="hr-logo">
+          <img *ngIf="!isExpanded" src="assets/images/hr-icon.svg" alt="HR Icon" class="hr-icon">
+        </div>
+        <h2 *ngIf="isExpanded" class="sidenav-title">人事管理</h2>
+      </div>
+
+      <!-- 导航菜单 -->
+      <mat-nav-list class="nav-list">
+        <!-- 人事看板 -->
+        <a mat-list-item routerLink="/hr/dashboard" routerLinkActive="active-link">
+          <mat-icon matListItemIcon>dashboard</mat-icon>
+          <span matListItemTitle *ngIf="isExpanded">人事看板</span>
+        </a>
+
+        <!-- 员工信息管理 -->
+        <a mat-list-item routerLink="/hr/employee-records" routerLinkActive="active-link">
+          <mat-icon matListItemIcon>people</mat-icon>
+          <span matListItemTitle *ngIf="isExpanded">员工信息管理</span>
+        </a>
+
+        <!-- 设计师详情 -->
+        <a mat-list-item routerLink="/hr/designer-profile/1" routerLinkActive="active-link">
+          <mat-icon matListItemIcon>person</mat-icon>
+          <span matListItemTitle *ngIf="isExpanded">设计师详情</span>
+        </a>
+
+        <!-- 考勤统计 -->
+        <a mat-list-item routerLink="/hr/attendance" routerLinkActive="active-link">
+          <mat-icon matListItemIcon>event_note</mat-icon>
+          <span matListItemTitle *ngIf="isExpanded">考勤统计</span>
+        </a>
+
+        <!-- 资产管理 -->
+        <a mat-list-item routerLink="/hr/assets" routerLinkActive="active-link">
+          <mat-icon matListItemIcon>inventory</mat-icon>
+          <span matListItemTitle *ngIf="isExpanded">资产管理</span>
+        </a>
+      </mat-nav-list>
+
+      <!-- 导航栏底部 -->
+      <div class="sidenav-footer">
+        <button mat-icon-button (click)="toggleSidenav()" matTooltip="{{isExpanded ? '收起菜单' : '展开菜单'}}">
+          <mat-icon>{{isExpanded ? 'chevron_left' : 'chevron_right'}}</mat-icon>
+        </button>
+      </div>
+    </mat-sidenav>
+
+    <!-- 主内容区 -->
+    <mat-sidenav-content class="sidenav-content">
+      <router-outlet></router-outlet>
+    </mat-sidenav-content>
+  </mat-sidenav-container>
+</div>

+ 112 - 0
src/app/pages/hr/hr-layout/hr-layout.scss

@@ -0,0 +1,112 @@
+.hr-layout-container {
+  height: 100vh;
+  display: flex;
+  flex-direction: column;
+}
+
+.sidenav-container {
+  flex: 1;
+  height: 100%;
+}
+
+.sidenav {
+  background-color: #ffffff;
+  border-right: 1px solid #e0e0e0;
+  transition: width 0.3s ease;
+  
+  &.expanded {
+    width: 250px;
+  }
+  
+  &.collapsed {
+    width: 70px;
+  }
+}
+
+.sidenav-header {
+  display: flex;
+  align-items: center;
+  padding: 16px;
+  border-bottom: 1px solid #e0e0e0;
+  background-color: #1a3a6e; /* 深蓝色 */
+  color: white;
+  height: 64px;
+}
+
+.logo-container {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+.hr-logo {
+  height: 32px;
+  margin-right: 12px;
+}
+
+.hr-icon {
+  height: 32px;
+}
+
+.sidenav-title {
+  margin: 0;
+  font-size: 18px;
+  font-weight: 500;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+.nav-list {
+  padding-top: 8px;
+}
+
+.mat-list-item {
+  border-radius: 0 24px 24px 0;
+  margin: 6px 10px 6px 0; /* 略微放大留白 */
+  position: relative;
+  
+  &:hover {
+    background-color: rgba(22, 93, 255, 0.06);
+  }
+}
+
+.active-link {
+  background-color: rgba(22, 93, 255, 0.12);
+  color: #165DFF;
+  position: relative;
+  
+  &::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    height: 3px;
+    background: linear-gradient(90deg, #165DFF, #7c3aed);
+  }
+  
+  .mat-icon {
+    color: #165DFF;
+  }
+}
+
+.mat-icon {
+  color: #616161;
+}
+
+.sidenav-footer {
+  position: absolute;
+  bottom: 0;
+  width: 100%;
+  display: flex;
+  justify-content: center;
+  padding: 16px 0;
+  border-top: 1px solid #e0e0e0;
+}
+
+.sidenav-content {
+  background-color: #f5f5f5;
+  padding: 20px;
+  overflow-y: auto;
+}

+ 33 - 0
src/app/pages/hr/hr-layout/hr-layout.ts

@@ -0,0 +1,33 @@
+import { Component } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { RouterModule } from '@angular/router';
+import { MatSidenavModule } from '@angular/material/sidenav';
+import { MatListModule } from '@angular/material/list';
+import { MatIconModule } from '@angular/material/icon';
+import { MatButtonModule } from '@angular/material/button';
+import { MatToolbarModule } from '@angular/material/toolbar';
+import { MatTooltipModule } from '@angular/material/tooltip';
+
+@Component({
+  selector: 'app-hr-layout',
+  standalone: true,
+  imports: [
+    CommonModule,
+    RouterModule,
+    MatSidenavModule,
+    MatListModule,
+    MatIconModule,
+    MatButtonModule,
+    MatToolbarModule,
+    MatTooltipModule
+  ],
+  templateUrl: './hr-layout.html',
+  styleUrls: ['./hr-layout.scss']
+})
+export class HrLayout {
+  isExpanded = true;
+
+  toggleSidenav() {
+    this.isExpanded = !this.isExpanded;
+  }
+}

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików