徐福静0235668 пре 1 дан
родитељ
комит
e8881053b8

+ 137 - 37
src/app/pages/hr/attendance/attendance.html

@@ -82,20 +82,27 @@
       <button 
         mat-raised-button 
         [class.active]="!showGanttView()" 
-        (click)="showGanttView.set(false)"
-        class="view-btn"
-      >
-        <mat-icon>calendar_today</mat-icon>
-        考勤日历
+        (click)="toggleView('attendance')"
+        class="view-btn">
+        考勤视图
       </button>
       <button 
         mat-raised-button 
         [class.active]="showGanttView()" 
-        (click)="toggleView()"
-        class="view-btn"
-      >
-        <mat-icon>timeline</mat-icon>
-        任务甘特图
+        (click)="toggleView('task')"
+        class="view-btn">
+        任务视图
+      </button>
+      <button 
+        mat-raised-button 
+        (click)="exportAttendanceData()"
+        class="export-btn">
+        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+          <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
+          <polyline points="7 10 12 15 17 10"></polyline>
+          <line x1="12" y1="15" x2="12" y2="3"></line>
+        </svg>
+        导出数据
       </button>
     </div>
 
@@ -286,27 +293,70 @@
       </div>
     </div>
     } @else {
-      <!-- 甘特图视图 -->
+      <!-- 考勤甘特图视图 -->
       <div class="gantt-section">
         <div class="gantt-header">
-          <h2>订单任务甘特图</h2>
+          <h2>员工考勤甘特图</h2>
           <div class="gantt-controls">
-            <button 
-              mat-raised-button 
-              [class.active]="ganttScale() === 'week'" 
-              (click)="setGanttScale('week')"
-              class="scale-btn"
-            >
-              周视图
-            </button>
-            <button 
-              mat-raised-button 
-              [class.active]="ganttScale() === 'month'" 
-              (click)="setGanttScale('month')"
-              class="scale-btn"
+            <!-- 显示模式切换 -->
+            <div class="gantt-mode-buttons">
+              <button 
+                mat-raised-button 
+                [class.active]="ganttMode() === 'attendance'" 
+                (click)="setGanttMode('attendance')"
+                class="mode-btn"
+              >
+                考勤状态
+              </button>
+              <button 
+                mat-raised-button 
+                [class.active]="ganttMode() === 'workload'" 
+                (click)="setGanttMode('workload')"
+                class="mode-btn"
+              >
+                工作负荷
+              </button>
+            </div>
+            
+            <!-- 时间尺度切换 -->
+            <div class="gantt-scale-buttons">
+              <button 
+                mat-raised-button 
+                [class.active]="ganttScale() === 'day'" 
+                (click)="setGanttScale('day')"
+                class="scale-btn"
+              >
+                日视图
+              </button>
+              <button 
+                mat-raised-button 
+                [class.active]="ganttScale() === 'week'" 
+                (click)="setGanttScale('week')"
+                class="scale-btn"
+              >
+                周视图
+              </button>
+              <button 
+                mat-raised-button 
+                [class.active]="ganttScale() === 'month'" 
+                (click)="setGanttScale('month')"
+                class="scale-btn"
+              >
+                月视图
+              </button>
+            </div>
+            
+            <!-- 部门筛选 -->
+            <select 
+              [(ngModel)]="selectedDepartment" 
+              (change)="onDepartmentChange()"
+              class="department-select"
             >
-              月视图
-            </button>
+              <option value="all">全部部门</option>
+              @for (dept of departments(); track dept) {
+                <option [value]="dept">{{ dept }}</option>
+              }
+            </select>
           </div>
         </div>
         
@@ -314,18 +364,68 @@
         
         <!-- 图例说明 -->
         <div class="gantt-legend">
-          <div class="legend-item">
-            <div class="legend-color" style="background-color: #22c55e;"></div>
-            <span>已完成 (100%)</span>
-          </div>
-          <div class="legend-item">
-            <div class="legend-color" style="background-color: #f59e0b;"></div>
-            <span>进展良好 (≥75%)</span>
+          @if (ganttMode() === 'attendance') {
+            <div class="legend-item">
+              <div class="legend-color normal"></div>
+              <span>正常出勤</span>
+            </div>
+            <div class="legend-item">
+              <div class="legend-color late"></div>
+              <span>迟到</span>
+            </div>
+            <div class="legend-item">
+              <div class="legend-color early"></div>
+              <span>早退</span>
+            </div>
+            <div class="legend-item">
+              <div class="legend-color absent"></div>
+              <span>旷工</span>
+            </div>
+            <div class="legend-item">
+              <div class="legend-color leave"></div>
+              <span>请假</span>
+            </div>
+            <div class="legend-item">
+              <div class="legend-color overtime"></div>
+              <span>加班</span>
+            </div>
+          } @else {
+            <div class="legend-item">
+              <div class="legend-color" style="background-color: #22c55e;"></div>
+              <span>工作负荷正常 (≤8h)</span>
+            </div>
+            <div class="legend-item">
+              <div class="legend-color" style="background-color: #f59e0b;"></div>
+              <span>工作负荷较高 (8-10h)</span>
+            </div>
+            <div class="legend-item">
+              <div class="legend-color" style="background-color: #ef4444;"></div>
+              <span>工作负荷过高 (>10h)</span>
+            </div>
+          }
+        </div>
+        
+        <!-- 统计信息 -->
+        <div class="gantt-stats">
+          <div class="stat-item">
+            <span class="stat-label">显示员工:</span>
+            <span class="stat-value">{{ filteredEmployees().length }}人</span>
           </div>
-          <div class="legend-item">
-            <div class="legend-color" style="background-color: #ef4444;"></div>
-            <span>需要关注 (<75%)</span>
+          <div class="stat-item">
+            <span class="stat-label">时间范围:</span>
+            <span class="stat-value">{{ getTimeRangeText() }}</span>
           </div>
+          @if (ganttMode() === 'attendance') {
+            <div class="stat-item">
+              <span class="stat-label">平均出勤率:</span>
+              <span class="stat-value">{{ getAverageAttendanceRate() }}%</span>
+            </div>
+          } @else {
+            <div class="stat-item">
+              <span class="stat-label">平均工时:</span>
+              <span class="stat-value">{{ getAverageWorkHours() }}h</span>
+            </div>
+          }
         </div>
       </div>
     }

+ 94 - 26
src/app/pages/hr/attendance/attendance.scss

@@ -307,6 +307,8 @@ $transition: all 0.2s ease;
     justify-content: space-between;
     align-items: center;
     margin-bottom: 20px;
+    flex-wrap: wrap;
+    gap: 16px;
     
     h2 {
       font-size: 20px;
@@ -317,33 +319,64 @@ $transition: all 0.2s ease;
     
     .gantt-controls {
       display: flex;
-      gap: 8px;
-      background-color: $bg-tertiary;
-      border-radius: $border-radius;
-      padding: 4px;
+      gap: 16px;
+      align-items: center;
+      flex-wrap: wrap;
+      
+      .gantt-mode-buttons,
+      .gantt-scale-buttons {
+        display: flex;
+        gap: 4px;
+        background-color: $bg-tertiary;
+        border-radius: $border-radius;
+        padding: 4px;
+        
+        .mode-btn,
+        .scale-btn {
+          padding: 8px 16px;
+          border-radius: $border-radius;
+          font-size: 14px;
+          font-weight: 500;
+          transition: $transition;
+          border: none;
+          cursor: pointer;
+          
+          &.active {
+            background-color: $primary-color;
+            color: white;
+            box-shadow: $shadow-sm;
+          }
+          
+          &:not(.active) {
+            background-color: transparent;
+            color: $text-secondary;
+            
+            &:hover {
+              background-color: $bg-secondary;
+              color: $text-primary;
+            }
+          }
+        }
+      }
       
-      .scale-btn {
-        padding: 8px 16px;
+      .department-select {
+        padding: 8px 12px;
+        border: 1px solid $border-color;
         border-radius: $border-radius;
+        background-color: $bg-primary;
+        color: $text-primary;
         font-size: 14px;
-        font-weight: 500;
-        transition: $transition;
-        border: none;
         cursor: pointer;
+        transition: $transition;
         
-        &.active {
-          background-color: $primary-color;
-          color: white;
+        &:focus {
+          outline: none;
+          border-color: $primary-color;
+          box-shadow: 0 0 0 3px rgba($primary-color, 0.1);
         }
         
-        &:not(.active) {
-          background-color: transparent;
-          color: $text-secondary;
-          
-          &:hover {
-            background-color: $bg-secondary;
-            color: $text-primary;
-          }
+        &:hover {
+          border-color: $primary-light;
         }
       }
     }
@@ -351,19 +384,22 @@ $transition: all 0.2s ease;
   
   .gantt-chart {
     width: 100%;
-    height: 500px;
-    min-height: 400px;
+    height: 600px;
+    min-height: 500px;
     border-radius: $border-radius;
-    background-color: $bg-secondary;
+    background-color: $bg-primary;
     border: 1px solid $border-color;
+    margin-bottom: 20px;
   }
   
   .gantt-legend {
     display: flex;
     gap: 24px;
-    margin-top: 20px;
-    padding-top: 20px;
-    border-top: 1px solid $border-color;
+    margin-bottom: 20px;
+    padding: 16px;
+    background-color: $bg-secondary;
+    border-radius: $border-radius;
+    flex-wrap: wrap;
     
     .legend-item {
       display: flex;
@@ -376,6 +412,38 @@ $transition: all 0.2s ease;
         width: 16px;
         height: 16px;
         border-radius: 4px;
+        
+        &.normal { background-color: #22c55e; }
+        &.late { background-color: #f59e0b; }
+        &.early { background-color: #f97316; }
+        &.absent { background-color: #ef4444; }
+        &.leave { background-color: #8b5cf6; }
+        &.overtime { background-color: #06b6d4; }
+      }
+    }
+  }
+  
+  .gantt-stats {
+    display: flex;
+    gap: 24px;
+    padding: 16px;
+    background-color: $bg-secondary;
+    border-radius: $border-radius;
+    flex-wrap: wrap;
+    
+    .stat-item {
+      display: flex;
+      gap: 8px;
+      font-size: 14px;
+      
+      .stat-label {
+        color: $text-secondary;
+        font-weight: 500;
+      }
+      
+      .stat-value {
+        color: $text-primary;
+        font-weight: 600;
       }
     }
   }

+ 453 - 227
src/app/pages/hr/attendance/attendance.ts

@@ -1,4 +1,4 @@
-import { Component, OnInit, signal, computed, Inject, ViewChild, ElementRef, OnDestroy } from '@angular/core';
+import { Component, OnInit, signal, computed, Inject, ViewChild, ElementRef, OnDestroy, NgZone, ChangeDetectorRef, AfterViewInit } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { FormsModule } from '@angular/forms';
 import { MatButtonModule } from '@angular/material/button';
@@ -252,19 +252,54 @@ const generateMockEmployees = (): Employee[] => {
   ],
   templateUrl: './attendance.html',
   styleUrl: './attendance.scss'
-}) export class Attendance implements OnDestroy, OnInit {
+}) export class Attendance implements OnDestroy, OnInit, AfterViewInit {
   @ViewChild('ganttChartRef') ganttChartRef!: ElementRef;
   private ganttChart: any = null;
+  private onResize = () => {
+    if (this.ganttChart) {
+      this.ganttChart.resize();
+    }
+  };
+  private onChartClick = (params: any) => {
+    this.zone.run(() => {
+      const v = params?.value;
+      if (!v) return;
+      const start = new Date(v[1]);
+      const end = new Date(v[2]);
+      const employee = v[3];
+      const progress = v[4];
+      alert(`任务:${params.name}\n负责人:${employee}\n进度:${progress}%\n起止:${start.toLocaleDateString()} ~ ${end.toLocaleDateString()}`);
+    });
+  };
   showGanttView = signal(false);
-  ganttScale = signal<'week' | 'month'>('week');
+  ganttScale = signal<'day' | 'week' | 'month'>('week');
+  ganttMode = signal<'attendance' | 'workload'>('attendance');
+  selectedDepartment = 'all';
+  
   // 数据
-  attendanceData = signal<AttendanceModel[]>([]);
-  employees = signal<Employee[]>([]);
+  attendanceData = signal<AttendanceModel[]>(generateMockAttendanceData());
+  employees = signal<Employee[]>(generateMockEmployees());
+  
+  // 计算属性
+  departments = computed(() => {
+    const depts = new Set(this.employees().map(emp => emp.department));
+    return Array.from(depts).sort();
+  });
+  
+  filteredEmployees = computed(() => {
+    if (this.selectedDepartment === 'all') {
+      return this.employees();
+    }
+    return this.employees().filter(emp => emp.department === this.selectedDepartment);
+  });
   selectedView = signal<'day' | 'week' | 'month'>('month');
   selectedDate = signal<Date>(new Date());
   selectedEmployeeId = signal<string>('');
   selectedProjectId = signal<string>('');
   
+  // 通过computed缓存当月日历,避免模板每次变更检测都创建新数组
+  calendarDays = computed(() => this.computeCalendarDays());
+  
   // 获取日期tooltip信息
   getDayTooltip(day: any): string {
     if (!day.attendance) {
@@ -414,22 +449,29 @@ const generateMockEmployees = (): Employee[] => {
   // 显示的表格列
   displayedColumns = ['date', 'employeeName', 'status', 'workHours', 'projectName', 'actions'];
   
-  constructor(private dialog: MatDialog) {}
+  constructor(private dialog: MatDialog, private zone: NgZone, private cdr: ChangeDetectorRef) {}
   
   ngOnInit() {
-    // 加载模拟数据
-    this.attendanceData.set(generateMockAttendanceData());
-    this.employees.set(generateMockEmployees());
+    // 已在字段声明处初始化模拟数据,避免首次变更检测期间的状态跃迁导致 ExpressionChanged
   }
-
+  
+  ngAfterViewInit() {
+    // 稳定首次变更检测,避免控制流指令在初始化过程中值变化触发 NG0100
+    this.cdr.detectChanges();
+  }
+  
   ngOnDestroy() {
     if (this.ganttChart) {
+      this.ganttChart.off('click', this.onChartClick);
       this.ganttChart.dispose();
       this.ganttChart = null;
     }
+    window.removeEventListener('resize', this.onResize);
   }
   
-  // 切换视图(日/周/月)
+  // 移除 ngAfterViewInit 钩子
+  
+   // 切换视图(日/周/月)
   switchView(view: 'day' | 'week' | 'month') {
     this.selectedView.set(view);
     // 重置到当前日期
@@ -437,25 +479,113 @@ const generateMockEmployees = (): Employee[] => {
   }
 
   // 切换视图(考勤/任务)
-  toggleView() {
-    this.showGanttView.set(!this.showGanttView());
-    if (this.showGanttView()) {
-      setTimeout(() => this.initOrUpdateGantt(), 0);
-    } else {
+  toggleView(target?: 'attendance' | 'task') {
+    try {
+      const next = target ? (target === 'task') : !this.showGanttView();
+      this.showGanttView.set(next);
+      
+      if (next) {
+        // 等待DOM渲染后初始化图表
+        setTimeout(() => {
+          try {
+            this.initOrUpdateGantt();
+          } catch (error) {
+            console.error('甘特图初始化失败:', error);
+          }
+        }, 100);
+      } else {
+        this.cleanupGanttChart();
+      }
+    } catch (error) {
+      console.error('视图切换失败:', error);
+    }
+  }
+
+  // 清理甘特图资源
+  private cleanupGanttChart() {
+    try {
       if (this.ganttChart) {
+        this.ganttChart.off('click', this.onChartClick);
         this.ganttChart.dispose();
         this.ganttChart = null;
+        window.removeEventListener('resize', this.onResize);
       }
+    } catch (error) {
+      console.error('甘特图清理失败:', error);
     }
   }
 
   // 设置甘特时间尺度
-  setGanttScale(scale: 'week' | 'month') {
+  setGanttScale(scale: 'day' | 'week' | 'month') {
     if (this.ganttScale() !== scale) {
       this.ganttScale.set(scale);
       this.updateGantt();
     }
   }
+
+  // 设置甘特图显示模式
+  setGanttMode(mode: 'attendance' | 'workload') {
+    if (this.ganttMode() !== mode) {
+      this.ganttMode.set(mode);
+      this.updateGantt();
+    }
+  }
+
+  // 部门筛选变化
+  onDepartmentChange() {
+    this.updateGantt();
+  }
+
+  // 获取时间范围文本
+  getTimeRangeText(): string {
+    const date = this.selectedDate();
+    const scale = this.ganttScale();
+    
+    if (scale === 'day') {
+      return date.toLocaleDateString('zh-CN');
+    } else if (scale === 'week') {
+      const startOfWeek = new Date(date);
+      const day = startOfWeek.getDay();
+      const diff = startOfWeek.getDate() - day + (day === 0 ? -6 : 1);
+      startOfWeek.setDate(diff);
+      
+      const endOfWeek = new Date(startOfWeek);
+      endOfWeek.setDate(startOfWeek.getDate() + 6);
+      
+      return `${startOfWeek.toLocaleDateString('zh-CN')} - ${endOfWeek.toLocaleDateString('zh-CN')}`;
+    } else {
+      return `${date.getFullYear()}年${date.getMonth() + 1}月`;
+    }
+  }
+
+  // 获取平均出勤率
+  getAverageAttendanceRate(): number {
+    const employees = this.filteredEmployees();
+    if (employees.length === 0) return 0;
+    
+    const totalRate = employees.reduce((sum, emp) => {
+      const empAttendance = this.attendanceData().filter(att => att.employeeId === emp.id);
+      const normalDays = empAttendance.filter(att => att.status === '正常').length;
+      const rate = empAttendance.length > 0 ? (normalDays / empAttendance.length) * 100 : 0;
+      return sum + rate;
+    }, 0);
+    
+    return Math.round(totalRate / employees.length);
+  }
+
+  // 获取平均工时
+  getAverageWorkHours(): number {
+    const employees = this.filteredEmployees();
+    if (employees.length === 0) return 0;
+    
+    const totalHours = employees.reduce((sum, emp) => {
+      const empAttendance = this.attendanceData().filter(att => att.employeeId === emp.id);
+      const avgHours = empAttendance.reduce((h, att) => h + (att.workHours || 0), 0) / empAttendance.length;
+      return sum + (avgHours || 0);
+    }, 0);
+    
+    return Math.round((totalHours / employees.length) * 10) / 10;
+  }
   
   // 获取员工姓名
   getEmployeeName(employeeId: string): string {
@@ -510,32 +640,71 @@ const generateMockEmployees = (): Employee[] => {
     this.selectedDate.set(newDate);
   }
   
+  private isDialogOpening = false;
+
   // 打开补卡申请对话框
   openAttendanceDialog(attendance: AttendanceModel) {
-    const employee = this.employees().find(emp => emp.id === attendance.employeeId);
-    const dialogRef = this.dialog.open(AttendanceDialog, {
-      width: '500px',
-      maxWidth: '90vw',
-      disableClose: true,
-      data: {
-        ...attendance,
-        employeeName: employee ? employee.name : '未知员工'
-      }
-    });
+    // 防止重复点击
+    if (this.isDialogOpening) {
+      return;
+    }
     
-    dialogRef.afterClosed().subscribe(result => {
-      if (result) {
-        // 在实际应用中,这里会提交补卡申请到服务器
-        alert('补卡申请已提交,等待审核');
-      }
-    });
+    try {
+      this.isDialogOpening = true;
+      const employee = this.employees().find(emp => emp.id === attendance.employeeId);
+      
+      const dialogRef = this.dialog.open(AttendanceDialog, {
+        width: '500px',
+        maxWidth: '90vw',
+        disableClose: true,
+        panelClass: 'hr-dialog',
+        backdropClass: 'hr-dialog-backdrop',
+        data: {
+          ...attendance,
+          employeeName: employee ? employee.name : '未知员工'
+        }
+      });
+      
+      dialogRef.afterClosed().subscribe({
+        next: (result) => {
+          this.isDialogOpening = false;
+          if (result) {
+            // 在实际应用中,这里会提交补卡申请到服务器
+            alert('补卡申请已提交,等待审核');
+          }
+        },
+        error: (error) => {
+          this.isDialogOpening = false;
+          console.error('对话框关闭时出错:', error);
+        }
+      });
+    } catch (error) {
+      this.isDialogOpening = false;
+      console.error('打开补卡对话框失败:', error);
+      alert('打开对话框失败,请重试');
+    }
   }
   
   // 导出考勤数据
   exportAttendanceData(): void {
-    const data = this.filteredAttendance();
-    const csvContent = this.convertToCSV(data);
-    this.downloadCSV(csvContent, '考勤数据.csv');
+    try {
+      const data = this.filteredAttendance();
+      if (data.length === 0) {
+        alert('暂无数据可导出');
+        return;
+      }
+      
+      const csvContent = this.convertToCSV(data);
+      this.downloadCSV(csvContent, '考勤数据.csv');
+      
+      // 显示成功提示
+      setTimeout(() => {
+        alert(`成功导出 ${data.length} 条考勤记录`);
+      }, 100);
+    } catch (error) {
+      console.error('导出数据失败:', error);
+      alert('导出失败,请重试');
+    }
   }
 
   // 将数据转换为CSV格式
@@ -577,6 +746,11 @@ const generateMockEmployees = (): Employee[] => {
   
   // 获取日历数据
   getCalendarDays() {
+    return this.calendarDays();
+  }
+  
+  // 实际计算日历数据
+  private computeCalendarDays() {
     const year = this.selectedDate().getFullYear();
     const month = this.selectedDate().getMonth();
     
@@ -587,7 +761,7 @@ const generateMockEmployees = (): Employee[] => {
     // 获取当月第一天是星期几
     const firstDayIndex = firstDay.getDay();
     
-    const days = [];
+    const days: any[] = [];
     
     // 添加上月的最后几天
     for (let i = firstDayIndex; i > 0; i--) {
@@ -641,219 +815,271 @@ const generateMockEmployees = (): Employee[] => {
   }
 
   private initOrUpdateGantt(): void {
-    if (!this.ganttChartRef) return;
-    const el = this.ganttChartRef.nativeElement;
-    if (!this.ganttChart) {
-      this.ganttChart = echarts.init(el);
-      window.addEventListener('resize', () => {
-        this.ganttChart && this.ganttChart.resize();
-      });
+    if (!this.ganttChartRef?.nativeElement) {
+      console.warn('甘特图容器未找到');
+      return;
     }
-    this.updateGantt();
+    
+    const el = this.ganttChartRef.nativeElement;
+    
+    this.zone.runOutsideAngular(() => {
+      try {
+        if (!this.ganttChart) {
+          // 检查容器尺寸
+          if (el.offsetWidth === 0 || el.offsetHeight === 0) {
+            console.warn('甘特图容器尺寸为0,延迟初始化');
+            setTimeout(() => this.initOrUpdateGantt(), 200);
+            return;
+          }
+          
+          this.ganttChart = echarts.init(el);
+          window.addEventListener('resize', this.onResize);
+        }
+        this.updateGantt();
+      } catch (error) {
+        console.error('甘特图初始化失败:', error);
+      }
+    });
   }
 
   private updateGantt(): void {
-    if (!this.ganttChart) return;
-
-    // 生成甘特图数据 - 模拟订单任务数据
-    const tasks = [
-      {
-        name: '需求补充',
-        startDate: new Date(2024, 4, 1), // 2024-05-01
-        endDate: new Date(2024, 4, 5),   // 2024-05-05
-        progress: 33,
-        employee: '张三',
-        unfinishedItems: ['图纸上传(2/3)', '设计文档(0/1)', '客户确认(0/1)']
-      },
-      {
-        name: '方案设计',
-        startDate: new Date(2024, 4, 3),
-        endDate: new Date(2024, 4, 8),
-        progress: 75,
-        employee: '李四',
-        unfinishedItems: ['效果图(3/4)', '材料清单(1/1)']
-      },
-      {
-        name: '施工图绘制',
-        startDate: new Date(2024, 4, 6),
-        endDate: new Date(2024, 4, 12),
-        progress: 50,
-        employee: '王五',
-        unfinishedItems: ['平面图(1/2)', '立面图(2/4)', '节点图(0/3)']
-      },
-      {
-        name: '预算编制',
-        startDate: new Date(2024, 4, 10),
-        endDate: new Date(2024, 4, 15),
-        progress: 100,
-        employee: '赵六',
-        unfinishedItems: []
-      }
-    ];
-
-    const categories = tasks.map(t => t.name);
-    const DAY = 24 * 60 * 60 * 1000;
+    if (!this.ganttChart) {
+      console.warn('甘特图实例不存在');
+      return;
+    }
+    
+    try {
+      // 工作负荷模式和考勤状态模式使用相同的数据结构,只是颜色映射不同
 
-    const data = tasks.map((task, idx) => {
-      const start = task.startDate.getTime();
-      const end = task.endDate.getTime();
-      const progress = task.progress;
+      // 考勤状态甘特图
+      const employees = this.filteredEmployees();
+      const categories = employees.map(emp => emp.name);
       
-      return {
-        name: task.name,
-        value: [idx, start, end, task.employee, progress, task.unfinishedItems],
-        itemStyle: {
-        color: task?.progress === 100 ? '#22c55e' : (task?.progress && task.progress >= 75) ? '#f59e0b' : '#ef4444'
-      }
+      // 考勤状态颜色映射
+      const statusColorMap: Record<string, string> = {
+        '正常': '#22c55e',
+        '迟到': '#f59e0b', 
+        '早退': '#f97316',
+        '旷工': '#ef4444',
+        '请假': '#8b5cf6',
+        '加班': '#06b6d4'
       };
-    });
 
-    // 计算时间范围
-    const now = new Date();
-    const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
-    const todayTs = today.getTime();
-
-    let xMin: number;
-    let xMax: number;
-    let xSplitNumber: number;
-    let xLabelFormatter: (value: number) => string;
+      const DAY = 24 * 60 * 60 * 1000;
+      const now = new Date();
+      const selectedDate = this.selectedDate();
+      
+      // 计算时间范围
+      let xMin: number;
+      let xMax: number;
+      let xSplitNumber: number;
+      let xLabelFormatter: (value: number) => string;
 
-    if (this.ganttScale() === 'week') {
-      const day = today.getDay();
-      const diffToMonday = (day === 0 ? 6 : day - 1);
-      const startOfWeek = new Date(today.getTime() - diffToMonday * DAY);
-      const endOfWeek = new Date(startOfWeek.getTime() + 7 * DAY - 1);
-      xMin = startOfWeek.getTime();
-      xMax = endOfWeek.getTime();
-      xSplitNumber = 7;
-      const WEEK_LABELS = ['周日','周一','周二','周三','周四','周五','周六'];
-      xLabelFormatter = (val) => WEEK_LABELS[new Date(val).getDay()];
-    } else {
-      const startOfMonth = new Date(today.getFullYear(), today.getMonth(), 1);
-      const endOfMonth = new Date(today.getFullYear(), today.getMonth() + 1, 0, 23, 59, 59, 999);
-      xMin = startOfMonth.getTime();
-      xMax = endOfMonth.getTime();
-      xSplitNumber = 4;
-      xLabelFormatter = (val) => `第${Math.ceil(new Date(val).getDate() / 7)}周`;
-    }
+      if (this.ganttScale() === 'day') {
+        // 日视图:显示当天的小时
+        const startOfDay = new Date(selectedDate.getFullYear(), selectedDate.getMonth(), selectedDate.getDate());
+        const endOfDay = new Date(startOfDay.getTime() + DAY - 1);
+        xMin = startOfDay.getTime();
+        xMax = endOfDay.getTime();
+        xSplitNumber = 24;
+        xLabelFormatter = (val) => `${new Date(val).getHours()}:00`;
+      } else if (this.ganttScale() === 'week') {
+        // 周视图:显示一周的天数
+        const day = selectedDate.getDay();
+        const diffToMonday = (day === 0 ? 6 : day - 1);
+        const startOfWeek = new Date(selectedDate.getTime() - diffToMonday * DAY);
+        const endOfWeek = new Date(startOfWeek.getTime() + 7 * DAY - 1);
+        xMin = startOfWeek.getTime();
+        xMax = endOfWeek.getTime();
+        xSplitNumber = 7;
+        const WEEK_LABELS = ['周日','周一','周二','周三','周四','周五','周六'];
+        xLabelFormatter = (val) => WEEK_LABELS[new Date(val).getDay()];
+      } else {
+        // 月视图:显示一个月的周数
+        const startOfMonth = new Date(selectedDate.getFullYear(), selectedDate.getMonth(), 1);
+        const endOfMonth = new Date(selectedDate.getFullYear(), selectedDate.getMonth() + 1, 0, 23, 59, 59, 999);
+        xMin = startOfMonth.getTime();
+        xMax = endOfMonth.getTime();
+        xSplitNumber = 4;
+        xLabelFormatter = (val) => `第${Math.ceil(new Date(val).getDate() / 7)}周`;
+      }
 
-    const option = {
-      backgroundColor: 'transparent',
-      tooltip: {
-        trigger: 'item',
-        formatter: (params: any) => {
-          const v = params.value;
-          const start = new Date(v[1]);
-          const end = new Date(v[2]);
-          const progress = v[4];
-          const unfinished = v[5];
-          let html = `任务:${params.name}<br/>负责人:${v[3]}<br/>进度:${progress}%<br/>起止:${start.toLocaleDateString()} ~ ${end.toLocaleDateString()}`;
+      // 生成考勤数据
+      const data: any[] = [];
+      employees.forEach((emp, empIndex) => {
+        const empAttendance = this.attendanceData().filter(att => att.employeeId === emp.id);
+        
+        if (this.ganttScale() === 'day') {
+          // 日视图:显示工作时间段
+          const dayAttendance = empAttendance.find(att => {
+            const attDate = new Date(att.date);
+            return attDate.toDateString() === selectedDate.toDateString();
+          });
           
-          if (unfinished.length > 0) {
-            html += '<br/><br/>未完成项:<br/>' + unfinished.join('<br/>');
+          if (dayAttendance && dayAttendance.checkInTime && dayAttendance.checkOutTime) {
+            const checkIn = new Date(`${selectedDate.toDateString()} ${dayAttendance.checkInTime}`);
+            const checkOut = new Date(`${selectedDate.toDateString()} ${dayAttendance.checkOutTime}`);
+            
+            data.push({
+              name: emp.name,
+              value: [empIndex, checkIn.getTime(), checkOut.getTime(), dayAttendance.status, dayAttendance.workHours],
+              itemStyle: { color: statusColorMap[dayAttendance.status] || '#94a3b8' }
+            });
+          }
+        } else {
+          // 周视图和月视图:显示每天的考勤状态
+          const timeRange = this.ganttScale() === 'week' ? 7 : 30;
+          const startTime = this.ganttScale() === 'week' ? 
+            new Date(selectedDate.getTime() - ((selectedDate.getDay() === 0 ? 6 : selectedDate.getDay() - 1) * DAY)) :
+            new Date(selectedDate.getFullYear(), selectedDate.getMonth(), 1);
+            
+          for (let i = 0; i < timeRange; i++) {
+            const currentDay = new Date(startTime.getTime() + i * DAY);
+            const dayAttendance = empAttendance.find(att => {
+              const attDate = new Date(att.date);
+              return attDate.toDateString() === currentDay.toDateString();
+            });
+            
+            if (dayAttendance) {
+              const dayStart = new Date(currentDay.getFullYear(), currentDay.getMonth(), currentDay.getDate());
+              const dayEnd = new Date(dayStart.getTime() + DAY - 1);
+              
+              data.push({
+                name: emp.name,
+                value: [empIndex, dayStart.getTime(), dayEnd.getTime(), dayAttendance.status, dayAttendance.workHours],
+                itemStyle: { color: statusColorMap[dayAttendance.status] || '#94a3b8' }
+              });
+            }
           }
-          return html;
         }
-      },
-      grid: { left: 100, right: 64, top: 30, bottom: 30 },
-      xAxis: {
-        type: 'time',
-        min: xMin,
-        max: xMax,
-        splitNumber: xSplitNumber,
-        axisLine: { lineStyle: { color: '#e5e7eb' } },
-        axisLabel: { color: '#6b7280', formatter: xLabelFormatter },
-        splitLine: { lineStyle: { color: '#f1f5f9' } }
-      },
-      yAxis: {
-        type: 'category',
-        data: categories,
-        inverse: true,
-        axisLabel: {
-          color: '#374151',
-          margin: 8,
-          formatter: (val: string) => {
-            const task = tasks.find(t => t.name === val);
-            const color = task?.progress === 100 ? '#22c55e' : (task?.progress && task.progress >= 75) ? '#f59e0b' : '#ef4444';
-            const text = val.length > 16 ? val.slice(0, 16) + '…' : val;
-            return `{dot|●} ${text}`;
-          },
-          rich: {
-            dot: { color: '#ef4444' }
+      });
+
+      // 计算默认可视区域
+      const total = categories.length;
+      const visible = Math.min(total, 15);
+      const defaultEndPercent = total > 0 ? Math.min(100, (visible / total) * 100) : 100;
+      const todayTs = now.getTime();
+
+      const option = {
+        backgroundColor: 'transparent',
+        tooltip: {
+          trigger: 'item',
+          formatter: (params: any) => {
+            const v = params.value;
+            const start = new Date(v[1]);
+            const end = new Date(v[2]);
+            const status = v[3];
+            const workHours = v[4];
+            
+            if (this.ganttScale() === 'day') {
+              return `员工:${params.name}<br/>状态:${status}<br/>工作时间:${start.toLocaleTimeString('zh-CN', {hour: '2-digit', minute: '2-digit'})} - ${end.toLocaleTimeString('zh-CN', {hour: '2-digit', minute: '2-digit'})}<br/>工时:${workHours || 0}小时`;
+            } else {
+              return `员工:${params.name}<br/>日期:${start.toLocaleDateString('zh-CN')}<br/>状态:${status}<br/>工时:${workHours || 0}小时`;
+            }
           }
         },
-        axisTick: { show: false },
-        axisLine: { lineStyle: { color: '#e5e7eb' } }
-      },
+        grid: { left: 120, right: 64, top: 30, bottom: 30 },
+        xAxis: {
+          type: 'time',
+          min: xMin,
+          max: xMax,
+          splitNumber: xSplitNumber,
+          axisLine: { lineStyle: { color: '#e5e7eb' } },
+          axisLabel: { color: '#6b7280', formatter: xLabelFormatter },
+          splitLine: { lineStyle: { color: '#f1f5f9' } }
+        },
+        yAxis: {
+          type: 'category',
+          data: categories,
+          inverse: true,
+          axisLabel: {
+            color: '#374151',
+            margin: 8,
+            formatter: (val: string) => {
+              const text = val.length > 12 ? val.slice(0, 12) + '…' : val;
+              return text;
+            }
+          },
+          axisTick: { show: false },
+          axisLine: { lineStyle: { color: '#e5e7eb' } }
+        },
       dataZoom: [
-        { type: 'slider', yAxisIndex: 0, orient: 'vertical', right: 6, width: 14, start: 0, end: 100, zoomLock: false },
-        { type: 'inside', yAxisIndex: 0, zoomOnMouseWheel: true, moveOnMouseMove: true, moveOnMouseWheel: true }
-      ],
-      series: [
-        {
-          type: 'custom',
-          renderItem: (params: any, api: any) => {
-            const categoryIndex = api.value(0);
-            const start = api.coord([api.value(1), categoryIndex]);
-            const end = api.coord([api.value(2), categoryIndex]);
-            const height = Math.max(api.size([0, 1])[1] * 0.5, 8);
-            
-            // 主进度条
-            const rectShape = echarts.graphic.clipRectByRect({
-              x: start[0],
-              y: start[1] - height / 2,
-              width: Math.max(end[0] - start[0], 2),
-              height
-            }, {
-              x: params.coordSys.x,
-              y: params.coordSys.y,
-              width: params.coordSys.width,
-              height: params.coordSys.height
-            });
-
-            // 进度指示条
-            const progress = api.value(4);
-            const progressWidth = Math.max((end[0] - start[0]) * (progress / 100), 2);
-            const progressShape = echarts.graphic.clipRectByRect({
-              x: start[0],
-              y: start[1] - height / 2,
-              width: progressWidth,
-              height
-            }, {
-              x: params.coordSys.x,
-              y: params.coordSys.y,
-              width: params.coordSys.width,
-              height: params.coordSys.height
-            });
+          { 
+            type: 'slider', 
+            yAxisIndex: 0, 
+            orient: 'vertical', 
+            right: 6, 
+            width: 14, 
+            start: 0, 
+            end: defaultEndPercent, 
+            zoomLock: false 
+          },
+          { 
+            type: 'inside', 
+            yAxisIndex: 0, 
+            zoomOnMouseWheel: true, 
+            moveOnMouseMove: true, 
+            moveOnMouseWheel: true 
+          }
+        ],
+        series: [
+          {
+            type: 'custom',
+            renderItem: (params: any, api: any) => {
+              const categoryIndex = api.value(0);
+              const start = api.coord([api.value(1), categoryIndex]);
+              const end = api.coord([api.value(2), categoryIndex]);
+              const height = Math.max(api.size([0, 1])[1] * 0.6, 12);
+              
+              // 考勤状态条
+              const rectShape = echarts.graphic.clipRectByRect({
+                x: start[0],
+                y: start[1] - height / 2,
+                width: Math.max(end[0] - start[0], 2),
+                height
+              }, {
+                x: params.coordSys.x,
+                y: params.coordSys.y,
+                width: params.coordSys.width,
+                height: params.coordSys.height
+              });
 
-            return {
-              type: 'group',
-              children: [
+              // 今日标记线
+              const isToday = this.ganttScale() === 'day' || 
+                (api.value(1) <= todayTs && api.value(2) >= todayTs);
+              
+              const elements = [
                 {
                   type: 'rect',
                   shape: rectShape,
                   style: {
-                    fill: 'rgba(0,0,0,0.1)',
-                    stroke: 'transparent'
+                    ...api.style(),
+                    stroke: isToday ? '#3b82f6' : 'transparent',
+                    strokeWidth: isToday ? 2 : 0
                   }
-                },
-                {
-                  type: 'rect',
-                  shape: progressShape,
-                  style: api.style()
                 }
-              ]
-            };
-          },
-          encode: { x: [1, 2], y: 0 },
-          data,
-          itemStyle: { borderRadius: 4 },
-          emphasis: { focus: 'self' }
-        }
-      ]
-    };
+              ];
 
-    this.ganttChart.setOption(option, true);
-    this.ganttChart.resize();
+              return {
+                type: 'group',
+                children: elements
+              };
+            },
+            encode: { x: [1, 2], y: 0 },
+            data,
+            itemStyle: { borderRadius: 6 },
+            emphasis: { focus: 'self' }
+          }
+        ]
+      };
+
+      this.ganttChart.setOption(option, true);
+      // 绑定点击事件,避免重复绑定
+      this.ganttChart.off('click', this.onChartClick);
+      this.ganttChart.on('click', this.onChartClick);
+      this.ganttChart.resize();
+    } catch (error) {
+      console.error('甘特图更新失败:', error);
+    }
   }
 }

+ 16 - 132
src/app/pages/hr/dashboard/add-comparison-dialog.component.ts

@@ -42,7 +42,7 @@ export interface DialogData {
       添加对比项
     </h2>
     
-    <mat-dialog-content class="dialog-content">
+    <mat-dialog-content>
       <div class="form-section">
         <mat-form-field appearance="outline" class="full-width">
           <mat-label>项目名称</mat-label>
@@ -105,22 +105,20 @@ export interface DialogData {
         </mat-form-field>
       </div>
 
-      <div class="metrics-section">
-        <h3>绩效指标设置</h3>
-        <div class="metrics-grid">
-          @for (metric of data.availableMetrics; track metric) {
-            <mat-form-field appearance="outline">
-              <mat-label>{{ getMetricDisplayName(metric) }}</mat-label>
-              <input 
-                matInput 
-                [(ngModel)]="itemData.metrics[metric]" 
-                placeholder="0-100%"
-                pattern="[0-9]+%?"
-                (blur)="formatMetricValue(metric)">
-              <span matSuffix>%</span>
-            </mat-form-field>
-          }
-        </div>
+      <h3>绩效指标设置</h3>
+      <div class="metrics-grid">
+        @for (metric of data.availableMetrics; track metric) {
+          <mat-form-field appearance="outline">
+            <mat-label>{{ getMetricDisplayName(metric) }}</mat-label>
+            <input 
+              matInput 
+              [(ngModel)]="itemData.metrics[metric]" 
+              placeholder="0-100%"
+              pattern="[0-9]+%?"
+              (blur)="formatMetricValue(metric)">
+            <span matSuffix>%</span>
+          </mat-form-field>
+        }
       </div>
 
       <div class="preview-section">
@@ -153,121 +151,7 @@ export interface DialogData {
       </button>
     </mat-dialog-actions>
   `,
-  styles: [`
-    .dialog-content {
-      min-width: 500px;
-      max-width: 600px;
-      padding: 20px 0;
-    }
-
-    .form-section {
-      margin-bottom: 20px;
-    }
-
-    .full-width {
-      width: 100%;
-    }
-
-    .metrics-section {
-      margin: 24px 0;
-    }
-
-    .metrics-section h3 {
-      margin: 0 0 16px 0;
-      color: #333;
-      font-size: 16px;
-      font-weight: 500;
-    }
-
-    .metrics-grid {
-      display: grid;
-      grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
-      gap: 16px;
-    }
-
-    .preview-section {
-      margin-top: 24px;
-      padding: 16px;
-      background: #f5f5f5;
-      border-radius: 8px;
-    }
-
-    .preview-section h3 {
-      margin: 0 0 16px 0;
-      color: #333;
-      font-size: 16px;
-      font-weight: 500;
-    }
-
-    .item-preview {
-      background: white;
-      padding: 16px;
-      border-radius: 8px;
-      box-shadow: 0 2px 4px rgba(0,0,0,0.1);
-    }
-
-    .preview-header {
-      display: flex;
-      align-items: center;
-      margin-bottom: 12px;
-    }
-
-    .preview-header mat-icon {
-      margin-right: 12px;
-      font-size: 24px;
-      width: 24px;
-      height: 24px;
-    }
-
-    .preview-info h4 {
-      margin: 0;
-      font-size: 16px;
-      font-weight: 500;
-    }
-
-    .preview-category {
-      font-size: 12px;
-      color: #666;
-    }
-
-    .preview-metrics {
-      display: grid;
-      grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
-      gap: 8px;
-    }
-
-    .metric-preview {
-      display: flex;
-      justify-content: space-between;
-      padding: 4px 0;
-      border-bottom: 1px solid #eee;
-    }
-
-    .metric-name {
-      font-size: 12px;
-      color: #666;
-    }
-
-    .metric-value {
-      font-size: 12px;
-      font-weight: 500;
-      color: #333;
-    }
-
-    .tech-icon { color: #2196F3; }
-    .design-icon { color: #E91E63; }
-    .product-icon { color: #FF9800; }
-    .operation-icon { color: #4CAF50; }
-    .hr-icon { color: #9C27B0; }
-    .data-icon { color: #607D8B; }
-    .marketing-icon { color: #FF5722; }
-    .support-icon { color: #795548; }
-    .new-icon { color: #00BCD4; }
-
-    mat-dialog-actions {
-      padding: 16px 24px;
-    }
-  `]
+  styleUrls: ['../../../shared/styles/_hr-dialog.scss']
 })
 export class AddComparisonDialogComponent {
   itemData: ComparisonItemData = {

+ 133 - 27
src/app/pages/hr/dashboard/dashboard.scss

@@ -543,40 +543,39 @@
      }
    }
 
-/* 详情面板覆盖层样式 - 修复缩放适配问题 */
+/* 详情面板覆盖层样式 - 参考财务对账详情面板设计 */
 .detail-panel-overlay {
   position: fixed;
   top: 0;
   left: 0;
   right: 0;
   bottom: 0;
-  background: rgba(0, 0, 0, 0.5);
-  z-index: 1000;
+  background-color: rgba(0, 0, 0, 0.6);
   display: flex;
   align-items: center;
   justify-content: center;
-  padding: 10px;
-  overflow: auto;
+  z-index: 1000;
+  backdrop-filter: blur(4px);
+  animation: fadeIn 0.3s ease;
 
   .detail-panel-container {
-    width: 95vw;
-    max-width: 1400px;
-    height: 95vh;
-    max-height: none;
-    min-height: 600px;
-    background: #ffffff;
-    border-radius: 12px;
-    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
-    overflow: hidden;
-    animation: slideIn 0.3s ease-out;
+    background-color: #ffffff;
+    border-radius: 16px;
+    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15);
+    width: 90%;
+    max-width: 800px;
+    max-height: 90vh;
     display: flex;
     flex-direction: column;
+    overflow: hidden;
+    animation: slideIn 0.3s ease;
+    border: 1px solid #e2e8f0;
     
     /* 确保在任意缩放比例下都能正常显示 */
     @media (max-width: 768px) {
-      width: 98vw;
-      height: 98vh;
-      border-radius: 8px;
+      width: 95%;
+      margin: 10px;
+      border-radius: 12px;
     }
     
     /* 处理极小屏幕或高缩放比例 */
@@ -589,14 +588,19 @@
   }
 }
 
+@keyframes fadeIn {
+  from { opacity: 0; }
+  to { opacity: 1; }
+}
+
 @keyframes slideIn {
-  from {
+  from { 
     opacity: 0;
-    transform: scale(0.9) translateY(-20px);
+    transform: translateY(-20px);
   }
-  to {
+  to { 
     opacity: 1;
-    transform: scale(1) translateY(0);
+    transform: translateY(0);
   }
 }
 }
@@ -2917,7 +2921,8 @@
         gap: 15px;
         justify-content: flex-start;
         flex-wrap: wrap;
-        margin-top: 10px;
+        margin-top: 20px;
+        padding: 15px 0;
 
         .ios-button {
           border-radius: 25px;
@@ -2927,22 +2932,56 @@
           transition: all 0.3s ease;
           display: flex;
           align-items: center;
+          justify-content: center;
           gap: 8px;
           min-width: 140px;
-          justify-content: center;
+          min-height: 48px;
+          position: relative;
+          overflow: hidden;
 
           &:hover {
             transform: translateY(-2px);
             box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
           }
 
+          &:active {
+            transform: translateY(0);
+            transition: transform 0.1s ease;
+          }
+
           &.primary {
             background: linear-gradient(135deg, #007AFF, #5856D6);
             color: white;
             border: none;
+            box-shadow: 0 4px 15px rgba(0, 122, 255, 0.3);
+
+            &::before {
+              content: '';
+              position: absolute;
+              top: 0;
+              left: 0;
+              right: 0;
+              bottom: 0;
+              background: radial-gradient(circle, rgba(255, 255, 255, 0.3) 0%, transparent 70%);
+              opacity: 0;
+              transition: opacity 0.3s ease;
+              border-radius: inherit;
+            }
 
             &:hover {
               background: linear-gradient(135deg, #0056CC, #4A47B8);
+              box-shadow: 0 6px 20px rgba(0, 122, 255, 0.4);
+              
+              &::before {
+                opacity: 1;
+              }
+            }
+
+            &:active {
+              &::before {
+                opacity: 0.5;
+                background: radial-gradient(circle, rgba(255, 255, 255, 0.5) 0%, transparent 50%);
+              }
             }
 
             &:disabled {
@@ -2950,16 +2989,60 @@
               color: #8E8E93;
               transform: none;
               box-shadow: none;
+              cursor: not-allowed;
+              
+              &::before {
+                display: none;
+              }
             }
           }
 
           &.secondary {
-            background: transparent;
+            background: rgba(0, 122, 255, 0.05);
             color: #007AFF;
-            border: 2px solid #007AFF;
+            border: 2px solid rgba(0, 122, 255, 0.3);
+            box-shadow: 0 2px 8px rgba(0, 122, 255, 0.1);
+
+            &::before {
+              content: '';
+              position: absolute;
+              top: 0;
+              left: 0;
+              right: 0;
+              bottom: 0;
+              background: linear-gradient(135deg, rgba(0, 122, 255, 0.1), rgba(88, 86, 214, 0.1));
+              opacity: 0;
+              transition: opacity 0.3s ease;
+              border-radius: inherit;
+            }
 
             &:hover {
               background: rgba(0, 122, 255, 0.1);
+              border-color: rgba(0, 122, 255, 0.5);
+              box-shadow: 0 4px 15px rgba(0, 122, 255, 0.2);
+              
+              &::before {
+                opacity: 1;
+              }
+            }
+
+            &:active {
+              &::before {
+                opacity: 0.7;
+              }
+            }
+
+            &:disabled {
+              background: rgba(142, 142, 147, 0.05);
+              color: #8E8E93;
+              border-color: rgba(142, 142, 147, 0.3);
+              transform: none;
+              box-shadow: none;
+              cursor: not-allowed;
+              
+              &::before {
+                display: none;
+              }
             }
           }
 
@@ -2967,6 +3050,11 @@
             font-size: 18px;
             width: 18px;
             height: 18px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            flex-shrink: 0;
+            line-height: 1;
           }
 
           .loading-spinner {
@@ -2976,6 +3064,14 @@
             border-top: 2px solid white;
             border-radius: 50%;
             animation: spin 1s linear infinite;
+            flex-shrink: 0;
+          }
+
+          span {
+            line-height: 1;
+            white-space: nowrap;
+            display: flex;
+            align-items: center;
           }
         }
       }
@@ -3207,6 +3303,9 @@
           mat-card-content {
             padding: 25px;
             position: relative;
+            display: flex;
+            flex-direction: column;
+            min-height: 300px;
             
             .metric-icon-wrapper {
               position: absolute;
@@ -3243,6 +3342,10 @@
             }
 
             .metric-content {
+              flex: 1;
+              display: flex;
+              flex-direction: column;
+              
               .metric-header {
                 margin-bottom: 20px;
                 
@@ -3392,7 +3495,8 @@
               display: flex;
               justify-content: flex-end;
               gap: 10px;
-              margin-top: 15px;
+              margin-top: auto;
+              padding-top: 15px;
               
               button {
                 width: 36px;
@@ -3418,6 +3522,8 @@
                   display: flex;
                   align-items: center;
                   justify-content: center;
+                  line-height: 1;
+                  vertical-align: middle;
                 }
               }
             }

+ 48 - 7
src/app/pages/hr/dashboard/dashboard.ts

@@ -402,6 +402,9 @@ export class Dashboard implements OnInit, AfterViewInit {
     if (this.radarChart) {
       this.radarChart.destroy();
     }
+    if (this.comparisonChart) {
+      this.comparisonChart.destroy();
+    }
   }
 
   // 切换标签页
@@ -412,6 +415,11 @@ export class Dashboard implements OnInit, AfterViewInit {
     if (tab === 'visualization') {
       setTimeout(() => this.initializeCharts(), 100);
     }
+    
+    // 如果切换到绩效统计页面,初始化对比图表
+    if (tab === 'performance') {
+      setTimeout(() => this.initComparisonChart(), 100);
+    }
   }
 
   // 绩效指标数据
@@ -1944,6 +1952,7 @@ export class Dashboard implements OnInit, AfterViewInit {
     this.initLineChart();
     this.initRadarChart();
     this.initResignationChart();
+    this.initComparisonChart();
   }
 
   // 初始化职级分布饼图
@@ -2500,7 +2509,7 @@ export class Dashboard implements OnInit, AfterViewInit {
             <span class="label">趋势:</span>
             <span class="value trend-${metric.trend.type}">
               <mat-icon>${metric.trend.icon}</mat-icon>
-              ${metric.trend.value} ${metric.trend.label}
+              ${metric.trend.value}
             </span>
           </div>
         </div>
@@ -2536,17 +2545,22 @@ export class Dashboard implements OnInit, AfterViewInit {
       .metric-details-feedback .feedback-header {
         display: flex;
         align-items: center;
-        gap: 12px;
+        gap: 16px;
         margin-bottom: 20px;
         font-size: 18px;
         font-weight: 600;
         color: #333;
+        line-height: 1.4;
       }
       .metric-details-feedback .feedback-header mat-icon {
         color: #6366f1;
         font-size: 24px;
         width: 24px;
         height: 24px;
+        flex-shrink: 0;
+        display: inline-flex;
+        align-items: center;
+        justify-content: center;
       }
       .metric-details-feedback .metric-info {
         display: flex;
@@ -2571,7 +2585,17 @@ export class Dashboard implements OnInit, AfterViewInit {
         font-weight: 600;
         display: flex;
         align-items: center;
-        gap: 4px;
+        gap: 8px;
+        line-height: 1.4;
+      }
+      .metric-details-feedback .value mat-icon {
+        font-size: 18px;
+        width: 18px;
+        height: 18px;
+        flex-shrink: 0;
+        display: inline-flex;
+        align-items: center;
+        justify-content: center;
       }
       .metric-details-feedback .value.excellent {
         color: #10b981;
@@ -2653,7 +2677,7 @@ export class Dashboard implements OnInit, AfterViewInit {
           </div>
           <div class="summary-item">
             <span class="label">对比周期:</span>
-            <span class="value">${metric.trend.label}</span>
+            <span class="value">较上月</span>
           </div>
         </div>
       </div>
@@ -2678,17 +2702,22 @@ export class Dashboard implements OnInit, AfterViewInit {
       .metric-trend-feedback .feedback-header {
         display: flex;
         align-items: center;
-        gap: 12px;
+        gap: 16px;
         margin-bottom: 20px;
         font-size: 18px;
         font-weight: 600;
         color: #333;
+        line-height: 1.4;
       }
       .metric-trend-feedback .feedback-header mat-icon {
         color: #6366f1;
         font-size: 24px;
         width: 24px;
         height: 24px;
+        flex-shrink: 0;
+        display: inline-flex;
+        align-items: center;
+        justify-content: center;
       }
       .metric-trend-feedback .trend-chart-placeholder {
         background: #f8fafc;
@@ -2733,7 +2762,17 @@ export class Dashboard implements OnInit, AfterViewInit {
         font-weight: 600;
         display: flex;
         align-items: center;
-        gap: 4px;
+        gap: 8px;
+        line-height: 1.4;
+      }
+      .metric-trend-feedback .value mat-icon {
+        font-size: 18px;
+        width: 18px;
+        height: 18px;
+        flex-shrink: 0;
+        display: inline-flex;
+        align-items: center;
+        justify-content: center;
       }
       .metric-trend-feedback .trend-positive {
         color: #10b981;
@@ -3011,7 +3050,9 @@ export class Dashboard implements OnInit, AfterViewInit {
 
   addComparisonItem(): void {
     const dialogRef = this.dialog.open(AddComparisonDialogComponent, {
-      width: '600px',
+      width: '700px',
+      panelClass: 'hr-dialog',
+      backdropClass: 'hr-dialog-backdrop',
       data: {
         dimension: this.selectedComparisonDimension,
         availableMetrics: this.selectedComparisonMetric

+ 158 - 41
src/app/pages/hr/dashboard/resignation-detail-panel.component.scss

@@ -1,16 +1,18 @@
 .resignation-detail-panel {
   display: flex;
   flex-direction: column;
-  height: 100%;
+  height: 100vh;
+  max-height: 90vh;
   width: 100%;
   background: #ffffff;
-  border-radius: 12px;
-  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
+  border-radius: 16px;
+  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15);
   overflow: hidden;
+  border: 1px solid #e2e8f0;
   
   /* 确保在任意缩放比例下都能正常显示 */
   @media (max-width: 768px) {
-    border-radius: 8px;
+    border-radius: 12px;
   }
   
   @media (max-width: 480px) {
@@ -57,6 +59,29 @@
     button {
       color: white;
     }
+    // 统一头部关闭/图标按钮样式(与财务页对齐)
+    button.mat-mdc-icon-button,
+    button[mat-icon-button] {
+      color: #ffffff;
+      width: 36px;
+      height: 36px;
+      border-radius: 8px;
+      background: rgba(255, 255, 255, 0.12);
+      border: 1px solid rgba(255, 255, 255, 0.25);
+      transition: all 0.2s ease;
+      box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
+
+      &:hover {
+        background: rgba(255, 255, 255, 0.18);
+        transform: translateY(-1px);
+        box-shadow: 0 6px 16px rgba(0, 0, 0, 0.22);
+      }
+
+      &:active {
+        transform: translateY(0);
+        box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
+      }
+    }
   }
 
   .panel-content {
@@ -80,6 +105,7 @@
         flex: 1;
         overflow: hidden;
         min-height: 0;
+        max-height: calc(90vh - 200px);
       }
 
       .mat-mdc-tab-body {
@@ -89,13 +115,16 @@
       
       .mat-mdc-tab-body-content {
         height: 100%;
-        overflow: auto;
+        overflow-y: auto;
+        overflow-x: hidden;
+        padding-bottom: 20px;
       }
     }
 
     .tab-content {
       padding: 24px;
-      height: 100%;
+      height: auto;
+      min-height: 100%;
       overflow-y: auto;
       overflow-x: hidden;
       scrollbar-width: thin;
@@ -504,51 +533,139 @@
     align-items: center;
     justify-content: flex-end;
     gap: 12px;
-    padding: 20px 24px;
-    border-top: 1px solid #e0e0e0;
-    background: #fafafa;
-
+    padding: 16px 24px; // from 20px to 16px to match finance style
+    border-top: 1px solid #e2e8f0; // unify border color
+    background: #ffffff; // unify background
+    position: sticky; // make footer sticky
+    bottom: 0;
+    z-index: 1;
+  
     button {
       min-width: 100px;
     }
-  }
-}
-
-@media (max-width: 768px) {
-  .resignation-detail-panel {
-    .panel-content {
-      .stats-section {
-        grid-template-columns: 1fr;
+    
+    // 底部按钮统一风格
+    .mat-mdc-button,
+    .mat-mdc-raised-button {
+      border-radius: 8px;
+      padding: 8px 16px;
+      font-weight: 500;
+      transition: all 0.2s ease;
+      box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06);
+    }
+    
+    .mat-mdc-button {
+      color: #64748b;
+      background: #ffffff;
+      border: 1px solid #e2e8f0;
+    
+      &:hover {
+        background: #f8fafc;
+        transform: translateY(-1px);
       }
+    }
+    
+    .mat-mdc-raised-button.mat-primary {
+      color: #ffffff;
+      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+      box-shadow: 0 4px 12px rgba(102, 126, 234, 0.25);
+    
+      &:hover {
+        filter: brightness(1.03);
+        transform: translateY(-1px);
+        box-shadow: 0 8px 20px rgba(102, 126, 234, 0.35);
+      }
+    }
+  }
 
-      .analysis-section {
-        .impact-analysis {
-          grid-template-columns: 1fr;
-        }
+  // Tab 激活态与指示条颜色统一(从 .panel-footer 中移出,作用于整体)
+  & ::ng-deep .mat-mdc-tab-group .mdc-tab__text-label {
+    color: #64748b;
+    font-weight: 600;
+  }
+  & ::ng-deep .mat-mdc-tab-group .mdc-tab--active .mdc-tab__text-label {
+    color: #4f46e5;
+  }
+  & ::ng-deep .mat-mdc-tab-group .mdc-tab-indicator__content--underline {
+    border-color: #4f46e5 !important;
+  }
+  
+  // 统计卡片统一卡片化风格与悬浮反馈(从 .panel-footer 中移出,作用于整体)
+  & .panel-content .stats-section {
+    gap: 24px; // 统一网格间距 24px
+  }
+  & .panel-content .stats-section .stat-card {
+    padding: 16px; // unify padding
+    background: #ffffff; // ensure card background is white
+    border: 1px solid #e2e8f0; // unify border color
+  }
+  & .panel-content .stats-section .stat-card .stat-label {
+    color: #64748b; // unify text secondary color
+    font-weight: 500;
+  }
+  
+  // 分析区统一色彩与元素样式(从 .panel-footer 中移出,作用于整体)
+  & .panel-content .analysis-section h3 {
+    border-bottom: 2px solid #4f46e5; // unify accent color
+  }
+  & .panel-content .analysis-section .overview-text,
+  & .panel-content .analysis-section .impact-analysis .impact-section h4,
+  & .panel-content .analysis-section .impact-analysis .impact-section ul li,
+  & .panel-content .analysis-section .time-distribution .period-item .period-label,
+  & .panel-content .analysis-section .time-distribution .period-item .period-count {
+    color: #64748b; // unify text secondary color in analysis section
+  }
+  & .panel-content .analysis-section .impact-analysis .impact-section ul li::before {
+    color: #4f46e5; // unify bullet accent color
+  }
+  & .panel-content .analysis-section .time-distribution .period-item .period-bar {
+    background: #e2e8f0; // unify bar background color
+  }
+  & .panel-content .analysis-section .time-distribution .period-item .period-bar .bar-fill {
+    background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
+  }
+  & .panel-content .analysis-section ::ng-deep mat-chip {
+    background: #eef2ff;
+    color: #4f46e5;
+  }
+    
+    }
 
-        .time-distribution {
-          .period-item {
+    @media (max-width: 768px) {
+      .resignation-detail-panel {
+        .panel-content {
+          .stats-section {
             grid-template-columns: 1fr;
-            gap: 8px;
-            text-align: center;
           }
-        }
-      }
 
-      .resources {
-        .resource-grid {
-          grid-template-columns: 1fr;
+          .analysis-section {
+            .impact-analysis {
+              grid-template-columns: 1fr;
+            }
+
+            .time-distribution {
+              .period-item {
+                grid-template-columns: 1fr;
+                gap: 8px;
+                text-align: center;
+              }
+            }
+          }
+
+          .resources {
+            .resource-grid {
+              grid-template-columns: 1fr;
+            }
+          }
         }
-      }
-    }
 
-    .panel-footer {
-      flex-direction: column;
-      gap: 8px;
+        .panel-footer {
+          flex-direction: column;
+          gap: 8px;
 
-      button {
-        width: 100%;
+          button {
+            width: 100%;
+          }
+        }
       }
-    }
-  }
-}
+    }

+ 90 - 4
src/app/pages/hr/employee-records/employee-records.scss

@@ -69,9 +69,10 @@
     margin-bottom: $ios-spacing-lg;
     overflow: auto;
     border: 1px solid $ios-border;
-    
     .employee-table {
       width: 100%;
+      border-collapse: separate;
+      border-spacing: 0;
       
       th.mat-header-cell {
         background: $ios-background-secondary;
@@ -112,7 +113,6 @@
         }
       }
     }
-    
     .empty-state {
       display: flex;
       flex-direction: column;
@@ -295,7 +295,7 @@
   }
 }
 
-// 修复表格行高度对齐问题
+// 修复表格行高度对齐问题 - 统一表格线条样式
 .employee-records-container {
   // 当有敏感信息展开时,轻微扩大表格容器可视宽度(通过阴影与过渡体现)
   &.sensitive-expanded .employee-table-container {
@@ -305,6 +305,93 @@
 }
 
 .employee-table-container {
+  .employee-table {
+    border-collapse: separate;
+    border-spacing: 0;
+    width: 100%;
+
+    // 表头样式统一
+    th.mat-header-cell {
+      border-bottom: 2px solid #e2e8f0;
+      height: 56px;
+      vertical-align: middle;
+      text-align: left;
+      padding: 12px 16px;
+      background: #f8fafc;
+      font-weight: 600;
+      color: #374151;
+      position: sticky;
+      top: 0;
+      z-index: 10;
+    }
+
+    // 表格行样式统一
+    td.mat-cell, td.mat-footer-cell {
+      border-bottom: 1px solid #e2e8f0;
+      height: 64px;
+      vertical-align: middle;
+      padding: 12px 16px;
+      text-align: left;
+      transition: background-color 0.2s ease;
+    }
+
+    // 表格行悬停效果
+    tr.mat-row:hover td.mat-cell {
+      background-color: #f8fafc;
+    }
+
+    // 最后一行不显示底部边框
+    tr.mat-row:last-child td.mat-cell,
+    tr.mat-footer-row:last-child td.mat-footer-cell {
+      border-bottom: none;
+    }
+
+    // 确保所有单元格内容垂直居中
+    th.mat-header-cell,
+    td.mat-cell,
+    td.mat-footer-cell {
+      display: table-cell;
+      vertical-align: middle;
+    }
+
+    // 操作按钮列特殊处理
+    .actions-cell {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      height: 100%;
+      min-height: 64px;
+    }
+  }
+}
+.idcard,
+.bankcard {
+  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+  letter-spacing: 0.3px;
+  white-space: nowrap;
+  max-width: 160px;
+  display: inline-block;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  transition: max-width $ios-transition-duration ease;
+
+  &.expanded {
+    max-width: 360px;
+  }
+}
+
+.muted {
+  color: $ios-text-tertiary;
+}
+
+td.mat-cell {
+  .mat-icon {
+    color: $ios-primary;
+  }
+  button.mat-icon-button:hover .mat-icon {
+    color: rgba(0, 122, 255, 0.8);
+  }
+}
   .employee-table {
     .idcard,
     .bankcard {
@@ -335,7 +422,6 @@
       }
     }
   }
-}
 
 // 三点图标按钮样式 - iOS风格
 .more-actions-btn {

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

@@ -708,6 +708,7 @@ export class EmployeeRecords implements OnInit {
     const dialogRef = this.dialog.open(AddEmployeeDialog, {
       width: '700px',
       panelClass: 'hr-dialog',
+      backdropClass: 'hr-dialog-backdrop',
       data: { isEdit: false }
     });
     
@@ -729,6 +730,7 @@ export class EmployeeRecords implements OnInit {
     const dialogRef = this.dialog.open(AddEmployeeDialog, {
       width: '700px',
       panelClass: 'hr-dialog',
+      backdropClass: 'hr-dialog-backdrop',
       data: { employee, isEdit: true }
     });
     
@@ -747,6 +749,7 @@ export class EmployeeRecords implements OnInit {
     const dialogRef = this.dialog.open(OnboardingDialog, {
       width: '600px',
       panelClass: 'hr-dialog',
+      backdropClass: 'hr-dialog-backdrop',
       data: { employee }
     });
     
@@ -762,6 +765,7 @@ export class EmployeeRecords implements OnInit {
     const dialogRef = this.dialog.open(OffboardingDialog, {
       width: '600px',
       panelClass: 'hr-dialog',
+      backdropClass: 'hr-dialog-backdrop',
       data: { employee }
     });
     

+ 416 - 0
src/app/shared/styles/_hr-dialog.scss

@@ -0,0 +1,416 @@
+@import 'variables';
+@import 'ios-theme';
+
+// HR模块通用对话框样式
+// 基于新增员工面板的设计,提供统一的对话框外观
+
+// 对话框容器样式
+.hr-dialog {
+  .mat-mdc-dialog-container .mdc-dialog__surface {
+    border-radius: $ios-radius-lg;
+    background: $ios-card-background;
+    box-shadow: $ios-shadow-lg;
+    border: 1px solid $ios-border;
+    min-width: 500px;
+    max-width: 700px;
+  }
+
+  .mat-mdc-dialog-title {
+    color: $ios-text-primary;
+    font-weight: $ios-font-weight-semibold;
+    font-family: $ios-font-family;
+    display: flex;
+    align-items: center;
+    gap: $ios-spacing-sm;
+    padding: $ios-spacing-lg $ios-spacing-lg $ios-spacing-md;
+    margin: 0;
+    border-bottom: 1px solid $ios-border;
+
+    mat-icon {
+      color: $ios-primary;
+      font-size: 24px;
+      width: 24px;
+      height: 24px;
+    }
+  }
+
+  .mat-mdc-dialog-content {
+    padding: $ios-spacing-lg;
+    font-family: $ios-font-family;
+    max-height: 70vh;
+    overflow-y: auto;
+
+    // 表单样式
+    .employee-form,
+    .dialog-content {
+      display: flex;
+      flex-direction: column;
+      gap: $ios-spacing-md;
+    }
+
+    .form-row,
+    .form-section {
+      display: flex;
+      gap: $ios-spacing-md;
+      margin-bottom: $ios-spacing-sm;
+      
+      &.single-column {
+        flex-direction: column;
+      }
+      
+      mat-form-field {
+        flex: 1;
+        
+        &.full-width {
+          width: 100%;
+          margin-bottom: $ios-spacing-xs;
+        }
+      }
+    }
+
+    // 表单字段样式
+    mat-form-field {
+      margin-bottom: $ios-spacing-xs;
+      
+      .mat-mdc-form-field-outline {
+        border-radius: $ios-radius-md;
+        border-color: rgba(0, 122, 255, 0.2);
+        transition: all 0.3s ease;
+      }
+
+      &:hover .mat-mdc-form-field-outline {
+        border-color: rgba(0, 122, 255, 0.4);
+      }
+
+      &.mat-focused .mat-mdc-form-field-outline {
+        border-color: $ios-primary;
+        border-width: 2px;
+        box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.1);
+      }
+
+      .mat-mdc-form-field-label {
+        color: $ios-text-secondary;
+        font-family: $ios-font-family;
+        font-weight: $ios-font-weight-medium;
+      }
+
+      .mat-mdc-input-element {
+        color: $ios-text-primary;
+        font-family: $ios-font-family;
+        padding: $ios-spacing-sm;
+      }
+
+      .mat-mdc-form-field-error {
+        color: $ios-error;
+        font-family: $ios-font-family;
+        font-size: $ios-font-size-caption;
+        margin-top: $ios-spacing-xs;
+      }
+
+      // 选择框样式
+      .mat-mdc-select-value {
+        color: $ios-text-primary;
+        font-family: $ios-font-family;
+        padding: $ios-spacing-sm 0;
+      }
+
+      .mat-mdc-select-arrow {
+        color: $ios-text-secondary;
+        transition: transform 0.3s ease;
+      }
+
+      &.mat-focused .mat-mdc-select-arrow {
+        transform: rotate(180deg);
+        color: $ios-primary;
+      }
+
+      // 后缀图标样式
+      .mat-mdc-form-field-icon-suffix {
+        color: $ios-text-secondary;
+        transition: color 0.3s ease;
+      }
+
+      &:hover .mat-mdc-form-field-icon-suffix {
+        color: $ios-primary;
+      }
+    }
+
+    // 网格布局样式
+    .metrics-grid {
+      display: grid;
+      grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+      gap: $ios-spacing-md;
+    }
+
+    // 预览区域
+    .preview-section {
+      background: $ios-background-secondary;
+      border: 1px solid $ios-border;
+      border-radius: $ios-radius-lg;
+      padding: $ios-spacing-lg;
+      margin-top: $ios-spacing-lg;
+      transition: all 0.3s ease;
+
+      &:hover {
+        box-shadow: $ios-shadow-md;
+        border-color: rgba(0, 122, 255, 0.3);
+      }
+
+      .preview-title {
+        font-family: $ios-font-family;
+        font-weight: $ios-font-weight-semibold;
+        color: $ios-text-primary;
+        margin-bottom: $ios-spacing-md;
+        font-size: $ios-font-size-body;
+        display: flex;
+        align-items: center;
+        gap: $ios-spacing-xs;
+
+        &::before {
+          content: '👁️';
+          font-size: 16px;
+        }
+      }
+
+      .item-preview-card {
+        background: white;
+        border: 1px solid $ios-border;
+        border-radius: $ios-radius-md;
+        padding: $ios-spacing-lg;
+        box-shadow: $ios-shadow-sm;
+        transition: all 0.3s ease;
+
+        &:hover {
+          transform: translateY(-2px);
+          box-shadow: $ios-shadow-md;
+        }
+
+        .preview-header {
+          display: flex;
+          align-items: center;
+          gap: $ios-spacing-md;
+          margin-bottom: $ios-spacing-md;
+
+          .preview-icon {
+            width: 32px;
+            height: 32px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            border-radius: $ios-radius-md;
+            background: rgba(0, 122, 255, 0.1);
+            transition: all 0.3s ease;
+
+            &:hover {
+              background: rgba(0, 122, 255, 0.2);
+              transform: scale(1.1);
+            }
+          }
+
+          .preview-info {
+            flex: 1;
+
+            .preview-category {
+              font-size: $ios-font-size-caption;
+              color: $ios-text-secondary;
+              font-family: $ios-font-family;
+              text-transform: uppercase;
+              letter-spacing: 0.5px;
+              margin-bottom: $ios-spacing-xs;
+            }
+
+            .preview-name {
+              font-size: $ios-font-size-body;
+              color: $ios-text-primary;
+              font-family: $ios-font-family;
+              font-weight: $ios-font-weight-medium;
+            }
+          }
+        }
+
+        .metrics-preview {
+          display: flex;
+          flex-direction: column;
+          gap: $ios-spacing-sm;
+          padding: $ios-spacing-md;
+          background: $ios-background-secondary;
+          border-radius: $ios-radius-sm;
+
+          .metric-name {
+            font-size: $ios-font-size-caption;
+            color: $ios-text-secondary;
+            font-family: $ios-font-family;
+            text-transform: uppercase;
+            letter-spacing: 0.5px;
+          }
+
+          .metric-value {
+            font-size: $ios-font-size-title2;
+            font-weight: $ios-font-weight-bold;
+            color: $ios-primary;
+            font-family: $ios-font-family;
+            display: flex;
+            align-items: baseline;
+            gap: $ios-spacing-xs;
+
+            .metric-unit {
+              font-size: $ios-font-size-caption;
+              color: $ios-text-secondary;
+              font-weight: $ios-font-weight-regular;
+            }
+          }
+        }
+      }
+    }
+
+    // 分组标题样式
+    h3 {
+      margin: $ios-spacing-lg 0 $ios-spacing-md 0;
+      color: $ios-text-primary;
+      font-size: $ios-font-size-lg;
+      font-weight: $ios-font-weight-semibold;
+      font-family: $ios-font-family;
+
+      &:first-child {
+        margin-top: 0;
+      }
+    }
+  }
+
+  .mat-mdc-dialog-actions {
+    padding: $ios-spacing-md $ios-spacing-lg $ios-spacing-lg;
+    border-top: 1px solid $ios-border;
+    gap: $ios-spacing-sm;
+
+    .mat-mdc-button {
+      font-family: $ios-font-family;
+      border-radius: $ios-radius-md;
+      padding: $ios-spacing-sm $ios-spacing-lg;
+      
+      &[color="primary"] {
+        background: $ios-primary;
+        color: white;
+        
+        &:hover {
+          background: rgba(0, 122, 255, 0.9);
+        }
+        
+        &:disabled {
+          background: $ios-text-tertiary;
+          color: white;
+        }
+      }
+      
+      &:not([color]) {
+        color: $ios-text-secondary;
+        
+        &:hover {
+          background: rgba(0, 122, 255, 0.05);
+          color: $ios-primary;
+        }
+      }
+    }
+
+    .mat-mdc-raised-button {
+      box-shadow: $ios-shadow-sm;
+      
+      &:hover {
+        box-shadow: $ios-shadow-md;
+      }
+    }
+  }
+}
+
+// 图标颜色类
+.tech-icon { color: #2196F3; }
+.design-icon { color: #E91E63; }
+.product-icon { color: #FF9800; }
+.operation-icon { color: #4CAF50; }
+.hr-icon { color: #9C27B0; }
+.data-icon { color: #607D8B; }
+.marketing-icon { color: #FF5722; }
+.support-icon { color: #795548; }
+.new-icon { color: #00BCD4; }
+
+// 对话框背景遮罩样式
+.hr-dialog-backdrop {
+  background: rgba(0, 0, 0, 0.4);
+  backdrop-filter: blur(4px);
+}
+
+// 选择框下拉面板样式
+.mat-mdc-select-panel {
+  background: white;
+  border-radius: $ios-radius-md;
+  box-shadow: $ios-shadow-lg;
+  border: 1px solid $ios-border;
+  max-height: 300px;
+  overflow-y: auto;
+
+  .mat-mdc-option {
+    font-family: $ios-font-family;
+    padding: $ios-spacing-sm $ios-spacing-md;
+    transition: all 0.2s ease;
+    border-bottom: 1px solid rgba(0, 0, 0, 0.05);
+
+    &:last-child {
+      border-bottom: none;
+    }
+
+    &:hover {
+      background: rgba(0, 122, 255, 0.05);
+      color: $ios-primary;
+    }
+
+    &.mat-mdc-option-active {
+      background: rgba(0, 122, 255, 0.1);
+      color: $ios-primary;
+      font-weight: $ios-font-weight-medium;
+    }
+
+    .mat-mdc-option-text {
+      display: flex;
+      align-items: center;
+      gap: $ios-spacing-sm;
+
+      mat-icon {
+        font-size: 18px;
+        width: 18px;
+        height: 18px;
+      }
+    }
+  }
+}
+
+// 响应式调整
+@media (max-width: 768px) {
+  .hr-dialog {
+    .mat-mdc-dialog-container .mdc-dialog__surface {
+      min-width: 90vw;
+      max-width: 95vw;
+      margin: $ios-spacing-md;
+    }
+
+    .mat-mdc-dialog-content {
+      .form-row {
+        flex-direction: column;
+        
+        mat-form-field {
+          width: 100%;
+        }
+      }
+
+      .metrics-grid {
+        grid-template-columns: 1fr;
+      }
+    }
+  }
+
+  .mat-mdc-select-panel {
+    max-height: 250px;
+    
+    .mat-mdc-option {
+      padding: $ios-spacing-md;
+    }
+  }
+}

+ 109 - 0
src/styles.scss

@@ -43,3 +43,112 @@ body {
 
 html, body { height: 100%; }
 body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }
+
+
+/* HR Dialog Global Styles - 重新设计解决背景色重合问题 */
+.hr-dialog-backdrop {
+  background: rgba(0, 0, 0, 0.6);
+  backdrop-filter: blur(4px);
+  animation: fadeIn 0.3s ease;
+}
+
+@keyframes fadeIn {
+  from { opacity: 0; }
+  to { opacity: 1; }
+}
+
+.hr-dialog {
+  .mat-mdc-dialog-container .mdc-dialog__surface {
+    border-radius: 16px;
+    background: #ffffff;
+    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15);
+    border: 1px solid #e2e8f0;
+    animation: slideIn 0.3s ease;
+  }
+
+  @keyframes slideIn {
+    from { 
+      opacity: 0;
+      transform: translateY(-20px);
+    }
+    to { 
+      opacity: 1;
+      transform: translateY(0);
+    }
+  }
+
+  .mat-mdc-dialog-title {
+    margin: 0;
+    padding: 24px 32px;
+    color: #ffffff;
+    border-top-left-radius: 16px;
+    border-top-right-radius: 16px;
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    font-size: 22px;
+    font-weight: 600;
+    border-bottom: 1px solid #e2e8f0;
+  }
+
+  .mat-mdc-dialog-content {
+    padding: 24px 32px;
+    background: #ffffff;
+    max-height: 70vh;
+    overflow-y: auto;
+  }
+
+  .mat-mdc-dialog-actions {
+    padding: 16px 32px 24px;
+    border-top: 1px solid #e2e8f0;
+    background: #f8fafc;
+    display: flex;
+    justify-content: flex-end;
+    gap: 16px;
+  }
+
+  // 表单样式优化
+  .employee-form {
+    .form-row {
+      display: flex;
+      gap: 16px;
+      margin-bottom: 16px;
+      
+      mat-form-field {
+        flex: 1;
+      }
+    }
+
+    mat-form-field {
+      .mat-mdc-form-field-subscript-wrapper {
+        margin-top: 4px;
+      }
+    }
+  }
+
+  // 按钮样式优化
+  .mat-mdc-raised-button {
+    border-radius: 8px;
+    padding: 8px 24px;
+    font-weight: 500;
+    transition: all 0.3s ease;
+
+    &.mat-primary {
+      background: #667eea;
+      color: white;
+      
+      &:hover {
+        background: #5a6fd8;
+        transform: translateY(-1px);
+        box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
+      }
+    }
+
+    &:not(.mat-primary) {
+      background: #f1f5f9;
+      color: #64748b;
+      
+      &:hover {
+        background: #e2e8f0;
+      }
+    }
+  }
+}