Просмотр исходного кода

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

0235711 2 недель назад
Родитель
Сommit
5d3314f91f

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

@@ -55,6 +55,9 @@ import { Designers } from './pages/admin/designers/designers';
 import { Customers } from './pages/admin/customers/customers';
 import { FinancePage } from './pages/admin/finance/finance';
 
+// 演示页面
+import { DropdownDemoComponent } from './pages/shared/dropdown/dropdown-demo.component';
+
 export const routes: Routes = [
   // 客服路由
   {
@@ -156,6 +159,9 @@ export const routes: Routes = [
     ]
   },
 
+  // 演示页面路由
+  { path: 'dropdown-demo', component: DropdownDemoComponent, title: '下拉列表组件演示' },
+
   // 默认路由重定向到登录页
   { path: '', component: LoginPage, pathMatch: 'full' },
   { path: '**', redirectTo: '/customer-service/dashboard' }

+ 280 - 200
src/app/pages/hr/dashboard/dashboard.html

@@ -49,167 +49,212 @@
   <!-- 数据可视化页面 -->
   @if (activeTab === 'visualization') {
     <div class="visualization-page">
-      <div class="main-layout">
-        <!-- 左侧数据展示区域 -->
-        <div class="left-panel">
-          <!-- 职级分布饼图 -->
-          <mat-card class="chart-card">
-            <mat-card-header>
-              <mat-card-title>
-                <mat-icon>pie_chart</mat-icon>
-                职级分布
-              </mat-card-title>
-              <mat-card-subtitle>设计师职级占比分析</mat-card-subtitle>
-            </mat-card-header>
-            <mat-card-content>
-              <div class="pie-chart-container">
-                <canvas #pieChart></canvas>
-              </div>
-              <div class="chart-legend">
-                @for (item of rankDistribution; track item.level) {
-                  <div class="legend-item">
-                    <div class="legend-color" [style.background-color]="item.color"></div>
-                    <span>{{item.level}}: {{item.percentage}}% ({{item.count}}人)</span>
-                  </div>
-                }
-              </div>
-            </mat-card-content>
-          </mat-card>
+      <!-- 顶部三个图表一排显示 -->
+      <div class="top-charts-row">
+        <!-- 绩效总览雷达图 -->
+        <mat-card class="chart-card">
+          <mat-card-header>
+            <mat-card-title>
+              <mat-icon>radar</mat-icon>
+              绩效总览
+            </mat-card-title>
+            <mat-card-subtitle>各部门4个维度绩效对比</mat-card-subtitle>
+          </mat-card-header>
+          <mat-card-content>
+            <div class="radar-chart-container">
+              <canvas #radarChart></canvas>
+            </div>
+            <div class="radar-legend">
+              @for (dept of departmentPerformance; track dept.department) {
+                <div class="legend-item">
+                  <div class="legend-color" [style.background-color]="getDepartmentColor(dept.department)"></div>
+                  <span>{{dept.department}}</span>
+                </div>
+              }
+            </div>
+          </mat-card-content>
+        </mat-card>
 
-          <!-- 入职离职趋势折线图 -->
-          <mat-card class="chart-card">
-            <mat-card-header>
-              <mat-card-title>
-                <mat-icon>trending_up</mat-icon>
-                入职离职趋势
-              </mat-card-title>
-              <mat-card-subtitle>近6个月人员流动情况</mat-card-subtitle>
-            </mat-card-header>
-            <mat-card-content>
-              <div class="line-chart-container">
-                <canvas #lineChart></canvas>
-              </div>
-              <div class="key-notes">
-                <h4>关键节点</h4>
-                @for (note of keyNotes; track note.month) {
-                  <div class="note-item">
-                    <mat-icon class="note-icon">info</mat-icon>
-                    <span class="note-text">{{note.month}}:{{note.description}}</span>
-                  </div>
-                }
-              </div>
-            </mat-card-content>
-          </mat-card>
+        <!-- 职级分布饼图 -->
+        <mat-card class="chart-card">
+          <mat-card-header>
+            <mat-card-title>
+              <mat-icon>pie_chart</mat-icon>
+              职级分布
+            </mat-card-title>
+            <mat-card-subtitle>设计师职级占比分析</mat-card-subtitle>
+          </mat-card-header>
+          <mat-card-content>
+            <div class="pie-chart-container">
+              <canvas #pieChart></canvas>
+            </div>
+            <div class="chart-legend">
+              @for (item of rankDistribution; track item.level) {
+                <div class="legend-item">
+                  <div class="legend-color" [style.background-color]="item.color"></div>
+                  <span>{{item.level}}: {{item.percentage}}% ({{item.count}}人)</span>
+                </div>
+              }
+            </div>
+          </mat-card-content>
+        </mat-card>
 
-          <!-- 绩效总览雷达图 -->
-          <mat-card class="chart-card">
-            <mat-card-header>
-              <mat-card-title>
-                <mat-icon>radar</mat-icon>
-                绩效总览
-              </mat-card-title>
-              <mat-card-subtitle>各部门4个维度绩效对比</mat-card-subtitle>
-            </mat-card-header>
-            <mat-card-content>
-              <div class="radar-chart-container">
-                <canvas #radarChart></canvas>
-              </div>
-              <div class="radar-legend">
-                @for (dept of departmentPerformance; track dept.department) {
-                  <div class="legend-item">
-                    <div class="legend-color" [style.background-color]="getDepartmentColor(dept.department)"></div>
-                    <span>{{dept.department}}</span>
+        <!-- 入职离职趋势折线图 -->
+        <mat-card class="chart-card">
+          <mat-card-header>
+            <mat-card-title>
+              <mat-icon>trending_up</mat-icon>
+              入职离职趋势
+            </mat-card-title>
+            <mat-card-subtitle>近6个月人员流动情况</mat-card-subtitle>
+          </mat-card-header>
+          <mat-card-content>
+            <div class="line-chart-container">
+              <canvas #lineChart></canvas>
+            </div>
+            <div class="key-notes">
+              <h4>关键节点</h4>
+              @for (note of keyNotes; track note.month) {
+                <div class="note-item">
+                  <mat-icon class="note-icon">info</mat-icon>
+                  <span class="note-text">{{note.month}}:{{note.description}}</span>
+                </div>
+              }
+            </div>
+          </mat-card-content>
+        </mat-card>
+      </div>
+
+      <!-- iOS风格悬浮待办事项按钮 -->
+      <div class="ios-floating-todo-container">
+        <button 
+          class="ios-floating-todo-btn"
+          (click)="toggleTodoPanel()"
+          matTooltip="待办事项">
+          <div class="ios-btn-content">
+            <mat-icon>assignment</mat-icon>
+            @if (todoCount > 0) {
+              <span class="ios-todo-badge">{{todoCount}}</span>
+            }
+          </div>
+        </button>
+
+        <!-- iOS风格滑出的待办事项面板 -->
+        <div class="ios-todo-panel" [class.open]="isTodoPanelOpen">
+          <!-- 面板头部 -->
+          <div class="ios-panel-header">
+            <div class="ios-header-blur"></div>
+            <div class="ios-header-content">
+              <h3>待办事项</h3>
+              <button class="ios-close-btn" (click)="toggleTodoPanel()">
+                <mat-icon>close</mat-icon>
+              </button>
+            </div>
+          </div>
+          
+          <!-- 面板内容 -->
+          <div class="ios-panel-content" cdkDropList (cdkDropListDropped)="onTodoDrop($event)">
+            @for (todo of todoItems; track todo.id) {
+              <div class="ios-todo-item" 
+                   cdkDrag
+                   [class.completed]="todo.status === 'completed'"
+                   [class.in-progress]="todo.status === 'in_progress'">
+                
+                <!-- 拖拽手柄 -->
+                <div class="ios-drag-handle" cdkDragHandle>
+                  <div class="ios-drag-dots">
+                    <span></span>
+                    <span></span>
+                    <span></span>
                   </div>
-                }
-              </div>
-            </mat-card-content>
-          </mat-card>
+                </div>
 
-          <!-- 关键岗位空缺数 -->
-          <mat-card class="chart-card">
-            <mat-card-header>
-              <mat-card-title>
-                <mat-icon>work_off</mat-icon>
-                关键岗位空缺
-              </mat-card-title>
-              <mat-card-subtitle>紧急招聘需求</mat-card-subtitle>
-            </mat-card-header>
-            <mat-card-content>
-              <div class="vacancy-list">
-                @for (vacancy of keyVacancies; track vacancy.position) {
-                  <div class="vacancy-item" [class]="getPriorityClass(vacancy.priority)">
-                    <div class="vacancy-icon">
-                      <mat-icon>{{getVacancyIcon(vacancy.priority)}}</mat-icon>
+                <!-- 待办事项内容 -->
+                <div class="ios-todo-content">
+                  <!-- 图标和标题 -->
+                  <div class="ios-todo-header">
+                    <div class="ios-todo-icon" [class]="'type-' + todo.type">
+                      <mat-icon>
+                        @switch (todo.type) {
+                          @case ('resume') { description }
+                          @case ('onboarding') { person_add }
+                          @case ('resignation') { exit_to_app }
+                          @default { assignment }
+                        }
+                      </mat-icon>
                     </div>
-                    <div class="vacancy-info">
-                      <h4>{{vacancy.position}}</h4>
-                      <p>空缺:{{vacancy.count}}人</p>
-                      <span class="priority-badge" [class]="'priority-' + vacancy.priority">
-                        {{vacancy.priority === 'high' ? '紧急' : vacancy.priority === 'medium' ? '一般' : '低'}}
-                      </span>
+                    <div class="ios-todo-title-section">
+                      <h4 class="ios-todo-title">{{todo.title}}</h4>
+                      <p class="ios-todo-description">{{todo.description}}</p>
                     </div>
                   </div>
-                }
-              </div>
-            </mat-card-content>
-          </mat-card>
-        </div>
 
-        <!-- 右侧待办事项区域 -->
-        <div class="right-panel">
-          <!-- 待办事项按钮卡片 -->
-          <mat-card class="todo-card">
-            <mat-card-header>
-              <mat-card-title>
-                <mat-icon>assignment</mat-icon>
-                待办事项列表
-              </mat-card-title>
-              <mat-card-subtitle>点击按钮查看详情</mat-card-subtitle>
-            </mat-card-header>
-            <mat-card-content>
-              <!-- 待办事项按钮卡片 -->
-              <div class="todo-buttons">
-                <button mat-raised-button 
-                        class="todo-button priority-high" 
-                        (click)="toggleTodoList()"
-                        [class.expanded]="showTodoList">
-                  <mat-icon>assignment_turned_in</mat-icon>
-                  <span>查看待办事项</span>
-                  <mat-icon class="expand-icon">{{showTodoList ? 'expand_less' : 'expand_more'}}</mat-icon>
-                </button>
-              </div>
-              
-              <!-- 可展开的待办事项列表 -->
-              @if (showTodoList) {
-                <div class="todo-list" [@slideInOut]>
-                  @for (todo of todoList; track todo.id) {
-                    <div class="todo-item" [class]="getPriorityClass(todo.priority)">
-                      <div class="todo-content">
-                        <h4>{{todo.title}}</h4>
-                        <p>{{todo.description}}</p>
-                        <div class="todo-meta">
-                          <span class="priority-badge" [class]="'priority-' + todo.priority">
-                            {{todo.priority === 'high' ? '紧急' : todo.priority === 'medium' ? '一般' : '低'}}
-                          </span>
-                          <span class="due-date">{{todo.dueDate}}</span>
-                        </div>
-                      </div>
-                      <div class="todo-actions">
-                        <button mat-icon-button color="primary">
-                          <mat-icon>edit</mat-icon>
-                        </button>
-                        <button mat-icon-button color="accent">
-                          <mat-icon>check</mat-icon>
-                        </button>
-                      </div>
+                  <!-- 元数据 -->
+                  <div class="ios-todo-meta">
+                    <div class="ios-priority-indicator" [class]="'priority-' + todo.priority">
+                      <span class="ios-priority-dot"></span>
+                      <span class="ios-priority-text">
+                        {{todo.priority === 'high' ? '紧急' : todo.priority === 'medium' ? '一般' : '低'}}
+                      </span>
                     </div>
-                  }
+                    <div class="ios-status-indicator" [class]="'status-' + todo.status">
+                      {{todo.status === 'completed' ? '已完成' : todo.status === 'in_progress' ? '进行中' : '待处理'}}
+                    </div>
+                  </div>
+
+                  <!-- 操作按钮 -->
+                  <div class="ios-todo-actions">
+                    @if (todo.status !== 'completed') {
+                      <button class="ios-action-btn ios-complete-btn" 
+                              (click)="updateTodoStatus(todo, 'completed')">
+                        <mat-icon>check_circle</mat-icon>
+                        <span>完成</span>
+                      </button>
+                    }
+                    @if (todo.status === 'pending') {
+                      <button class="ios-action-btn ios-start-btn" 
+                              (click)="updateTodoStatus(todo, 'in_progress')">
+                        <mat-icon>play_circle</mat-icon>
+                        <span>开始</span>
+                      </button>
+                    }
+                    @if (todo.status === 'completed') {
+                      <button class="ios-action-btn ios-reset-btn" 
+                              (click)="updateTodoStatus(todo, 'pending')">
+                        <mat-icon>refresh</mat-icon>
+                        <span>重置</span>
+                      </button>
+                    }
+                  </div>
                 </div>
-              }
-            </mat-card-content>
-          </mat-card>
+              </div>
+            }
+            
+            <!-- 空状态 -->
+            @if (todoItems.length === 0) {
+              <div class="ios-empty-state">
+                <mat-icon>assignment_turned_in</mat-icon>
+                <h4>暂无待办事项</h4>
+                <p>所有任务都已完成</p>
+              </div>
+            }
+          </div>
+
+          <!-- 面板底部 -->
+          <div class="ios-panel-footer">
+            <button class="ios-add-todo-btn">
+              <mat-icon>add</mat-icon>
+              <span>添加新任务</span>
+            </button>
+          </div>
         </div>
 
+        <!-- 背景遮罩 -->
+        <div class="ios-panel-backdrop" 
+             [class.visible]="isTodoPanelOpen" 
+             (click)="toggleTodoPanel()"></div>
+      </div>
+
         <!-- 离职原因分析区域 -->
         <div class="resignation-analysis-section">
           <mat-card class="analysis-card">
@@ -318,7 +363,7 @@
                 <div class="reasons-chart-section">
                   <div class="chart-header">
                     <h3>离职原因占比分析</h3>
-                    <mat-button-toggle-group [(value)]="reasonsChartType">
+                    <mat-button-toggle-group [(value)]="reasonsChartType" (change)="onChartTypeChange()">
                       <mat-button-toggle value="pie">
                         <mat-icon>pie_chart</mat-icon>
                         饼图
@@ -341,7 +386,8 @@
 
                 <div class="reasons-list-section">
                   <h3>详细原因分析</h3>
-                  <div class="reasons-list">
+                  <div class="reasons-list-container">
+                    <div class="reasons-list">
                     @for (reason of resignationReasons; track reason.id) {
                       <div class="reason-item" [class]="'reason-' + reason.category">
                         <div class="reason-header">
@@ -388,32 +434,34 @@
                         </div>
                       </div>
                     }
+                    </div>
                   </div>
                 </div>
               </div>
 
-              <!-- 趋势分析 -->
-              <div class="resignation-trends">
-                <div class="trends-header">
-                  <h3>离职趋势分析</h3>
-                  <mat-button-toggle-group [(value)]="trendsTimeframe">
-                    <mat-button-toggle value="monthly">月度</mat-button-toggle>
-                    <mat-button-toggle value="quarterly">季度</mat-button-toggle>
-                    <mat-button-toggle value="yearly">年度</mat-button-toggle>
-                  </mat-button-toggle-group>
-                </div>
-                
-                <div class="trends-chart-container">
-                  <canvas #trendsChart width="800" height="300"></canvas>
-                </div>
-              </div>
+
             </mat-card-content>
           </mat-card>
         </div>
+    </div>
+  }
+
+  <!-- 离职原因详情面板 -->
+  @if (showDetailPanel && selectedReason && selectedDetailAnalysis && selectedImprovementPlan) {
+    <div class="detail-panel-overlay">
+      <div class="detail-panel-container">
+        <app-resignation-detail-panel
+          [reason]="selectedReason"
+          [detailAnalysis]="selectedDetailAnalysis"
+          [improvementPlan]="selectedImprovementPlan"
+          (close)="closeDetailPanel()"
+          (export)="exportDetailReport()">
+        </app-resignation-detail-panel>
       </div>
     </div>
   }
 
+
   <!-- 招聘流程优化页面 -->
   @if (activeTab === 'recruitment') {
     <div class="recruitment-page">
@@ -447,6 +495,21 @@
               </div>
             </div>
             
+            <!-- 分析进度显示 -->
+            @if (isAnalyzing) {
+              <div class="analysis-progress">
+                <div class="progress-header">
+                  <mat-icon class="analyzing-icon">psychology</mat-icon>
+                  <span class="progress-text">AI正在分析简历...</span>
+                </div>
+                <mat-progress-bar mode="determinate" [value]="analysisProgress"></mat-progress-bar>
+                <div class="progress-details">
+                  <span class="file-name">{{currentAnalysisFile?.name}}</span>
+                  <span class="progress-percentage">{{analysisProgress}}%</span>
+                </div>
+              </div>
+            }
+            
             <!-- 分析结果展示 -->
             @if (showAnalysisResults) {
               <div class="analysis-results">
@@ -545,47 +608,64 @@
             </div>
           </mat-card-header>
           <mat-card-content>
-            <div class="stages-timeline">
-              @for (stage of recruitmentStages; track stage.id) {
-                <div class="timeline-item" [class]="'status-' + stage.status" (click)="openStageDetails(stage)">
-                  <div class="timeline-marker">
-                    <mat-icon>{{stage.icon}}</mat-icon>
-                  </div>
-                  <div class="timeline-content">
-                    <div class="stage-header">
-                      <h5>{{stage.title}}</h5>
-                      <mat-chip [class]="'status-' + stage.status">{{stage.statusText}}</mat-chip>
-                    </div>
-                    <div class="stage-stats">
-                      <div class="stat-item">
-                        <mat-icon>people</mat-icon>
-                        <span>{{stage.candidateCount}}人</span>
-                      </div>
-                      <div class="stat-item">
-                        <mat-icon>percent</mat-icon>
-                        <span>通过率 {{stage.passRate}}%</span>
-                      </div>
+            <div class="stages-timeline-container" (scroll)="onTimelineScroll($event)">
+              <div class="stages-timeline" #stagesTimeline>
+                @for (stage of recruitmentStages; track stage.id) {
+                  <div class="timeline-item" [class]="'status-' + stage.status" (click)="openStageDetails(stage)">
+                    <div class="timeline-marker">
+                      <mat-icon>{{stage.icon}}</mat-icon>
                     </div>
-                    <div class="stage-details">
-                      <div class="detail-row">
-                        <span class="label">评估人:</span>
-                        <span class="value">{{stage.evaluator || '待分配'}}</span>
+                    <div class="timeline-content">
+                      <div class="stage-header">
+                        <h5>{{stage.title}}</h5>
+                        <mat-chip [class]="'status-' + stage.status">{{stage.statusText}}</mat-chip>
                       </div>
-                      <div class="detail-row">
-                        <span class="label">最近更新:</span>
-                        <span class="value">{{stage.lastUpdate | date:'MM-dd HH:mm'}}</span>
+                      <div class="stage-stats">
+                        <div class="stat-item">
+                          <mat-icon>people</mat-icon>
+                          <span>{{stage.candidateCount}}人</span>
+                        </div>
+                        <div class="stat-item">
+                          <mat-icon>percent</mat-icon>
+                          <span>通过率 {{stage.passRate}}%</span>
+                        </div>
                       </div>
-                      @if (stage.nextAction) {
-                        <div class="detail-row next-action">
-                          <mat-icon>arrow_forward</mat-icon>
-                          <span>{{stage.nextAction}}</span>
+                      <div class="stage-details">
+                        <div class="detail-row">
+                          <span class="label">评估人:</span>
+                          <span class="value">{{stage.evaluator || '待分配'}}</span>
+                        </div>
+                        <div class="detail-row">
+                          <span class="label">最近更新:</span>
+                          <span class="value">{{stage.lastUpdate | date:'MM-dd HH:mm'}}</span>
                         </div>
-                      }
+                        @if (stage.nextAction) {
+                          <div class="detail-row next-action">
+                            <mat-icon>arrow_forward</mat-icon>
+                            <span>{{stage.nextAction}}</span>
+                          </div>
+                        }
+                      </div>
                     </div>
+                    <div class="timeline-connector" *ngIf="!$last"></div>
                   </div>
-                  <div class="timeline-connector" *ngIf="!$last"></div>
+                }
+              </div>
+              <!-- 滑动指示器 -->
+              <div class="scroll-indicator" *ngIf="scrollIndicatorDots.length > 1">
+                <div class="scroll-dots">
+                  @for (dot of scrollIndicatorDots; track dot; let i = $index) {
+                    <div class="scroll-dot" 
+                         [class.active]="i === 0"
+                         (click)="scrollToPosition(i)">
+                    </div>
+                  }
                 </div>
-              }
+                <div class="scroll-hint">
+                  <mat-icon>swipe_vertical</mat-icon>
+                  <span>滑动查看更多</span>
+                </div>
+              </div>
             </div>
 
             <!-- 试用期跟踪关联入口 -->

Разница между файлами не показана из-за своего большого размера
+ 777 - 102
src/app/pages/hr/dashboard/dashboard.scss


+ 852 - 41
src/app/pages/hr/dashboard/dashboard.ts

@@ -14,10 +14,15 @@ import { MatSelectModule } from '@angular/material/select';
 import { MatInputModule } from '@angular/material/input';
 import { MatTableModule } from '@angular/material/table';
 import { MatButtonToggleModule } from '@angular/material/button-toggle';
+import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
+import { MatSnackBarModule, MatSnackBar } from '@angular/material/snack-bar';
 import { DragDropModule, CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
 import { Chart, ChartConfiguration, registerables } from 'chart.js';
 import { trigger, state, style, transition, animate } from '@angular/animations';
+import { DoubaoAiService, ResumeAnalysisRequest, ResumeAnalysisResponse } from '../../../services/doubao-ai.service';
+import { ResignationDetailPanelComponent, DetailAnalysis, ImprovementPlan } from './resignation-detail-panel.component';
 
+Chart.register(...registerables);
 // 数据模型定义
 export interface TodoItem {
   id: number;
@@ -152,7 +157,10 @@ export interface PerformanceMetric {
     MatInputModule,
     DragDropModule,
     MatTableModule,
-    MatButtonToggleModule
+    MatButtonToggleModule,
+    MatProgressSpinnerModule,
+    MatSnackBarModule,
+    ResignationDetailPanelComponent
   ],
   templateUrl: './dashboard.html',
   styleUrls: ['./dashboard.scss'],
@@ -172,10 +180,19 @@ export class Dashboard implements OnInit, AfterViewInit {
   @ViewChild('pieChart', { static: false }) pieChartRef!: ElementRef<HTMLCanvasElement>;
   @ViewChild('lineChart', { static: false }) lineChartRef!: ElementRef<HTMLCanvasElement>;
   @ViewChild('radarChart', { static: false }) radarChartRef!: ElementRef<HTMLCanvasElement>;
-  
+  @ViewChild('resignationChart', { static: false }) resignationChartRef!: ElementRef<HTMLCanvasElement>;
+
+  constructor(
+    private doubaoAiService: DoubaoAiService,
+    private snackBar: MatSnackBar
+  ) {
+    Chart.register(...registerables);
+  }
+
   private pieChart!: Chart;
   private lineChart!: Chart;
   private radarChart!: Chart;
+  private resignationChart!: Chart;
   // 当前激活的标签页
   activeTab: 'visualization' | 'recruitment' | 'performance' | 'onboarding' = 'visualization';
   
@@ -361,6 +378,7 @@ export class Dashboard implements OnInit, AfterViewInit {
     // 延迟初始化图表,确保DOM已渲染
     setTimeout(() => {
       this.initializeCharts();
+      this.initScrollIndicator();
     }, 100);
   }
 
@@ -620,7 +638,6 @@ export class Dashboard implements OnInit, AfterViewInit {
   // 离职原因分析相关属性
   resignationTimeRange: string = 'quarter';
   reasonsChartType: 'pie' | 'doughnut' | 'bar' = 'pie';
-  trendsTimeframe: 'monthly' | 'quarterly' | 'yearly' = 'monthly';
   
   totalResignations: number = 45;
   resignationRate: number = 8.5;
@@ -640,17 +657,23 @@ export class Dashboard implements OnInit, AfterViewInit {
     { id: 'senior', name: '高级', count: 7, selected: true }
   ];
   
+  // 详情面板相关
+  showDetailPanel = false;
+  selectedReason: any = null;
+  selectedDetailAnalysis: DetailAnalysis | null = null;
+  selectedImprovementPlan: ImprovementPlan | null = null;
+
   resignationReasons = [
     {
       id: 'salary',
       name: '薪资待遇',
       category: 'compensation',
       categoryName: '薪酬福利',
-      icon: 'attach_money',
+      icon: 'payments',
       percentage: 28.5,
-      count: 13,
+      count: 14,
       description: '薪资水平低于市场平均水平,缺乏有竞争力的薪酬体系',
-      trend: { direction: 'up', value: 5.2 }
+      trend: { direction: 'up', value: 3.2 }
     },
     {
       id: 'career',
@@ -709,9 +732,12 @@ export class Dashboard implements OnInit, AfterViewInit {
     }
   ];
 
-  // AI简历分析相关属性
+  // 简历分析相关属性
   isDragOver: boolean = false;
   showAnalysisResults: boolean = false;
+  isAnalyzing: boolean = false;
+  currentAnalysisFile: File | null = null;
+  analysisProgress: number = 0;
   
   matchDimensions: MatchDimension[] = [
     { id: 1, name: '建模经验', score: 92, level: 'high', icon: 'view_in_ar' },
@@ -858,11 +884,16 @@ export class Dashboard implements OnInit, AfterViewInit {
     }
   }
 
-  private handleFileUpload(file: File) {
+  private async handleFileUpload(file: File) {
     // 验证文件类型
-    const allowedTypes = ['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'];
+    const allowedTypes = [
+      'application/pdf', 
+      'application/msword', 
+      'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+      'text/plain'
+    ];
     if (!allowedTypes.includes(file.type)) {
-      this.showUploadError('不支持的文件格式,请上传PDF、DOC或DOCX文件');
+      this.showUploadError('不支持的文件格式,请上传PDF、DOC、DOCX或TXT文件');
       return;
     }
 
@@ -872,12 +903,61 @@ export class Dashboard implements OnInit, AfterViewInit {
       return;
     }
 
-    // 开始上传和分析
-    this.showUploadFeedback(file.name);
-    setTimeout(() => {
-      this.showAnalysisResults = true;
-      console.log('简历分析完成:', file.name);
-    }, 3000);
+    // 开始分析流程
+    this.currentAnalysisFile = file;
+    this.isAnalyzing = true;
+    this.analysisProgress = 0;
+    this.showAnalysisResults = false;
+
+    try {
+      // 显示开始分析的反馈
+      this.snackBar.open(`正在分析简历 "${file.name}"...`, '关闭', {
+        duration: 3000,
+        horizontalPosition: 'center',
+        verticalPosition: 'top'
+      });
+
+      // 模拟进度更新
+      this.updateAnalysisProgress(0);
+
+      // 提取文件文本内容
+      const resumeText = await this.doubaoAiService.extractTextFromFile(file);
+      
+      // 构建分析请求
+      const analysisRequest: ResumeAnalysisRequest = {
+        resumeText: resumeText,
+        jobPosition: '前端开发工程师', // 可以从当前招聘岗位获取
+        jobRequirements: [
+          '3年以上前端开发经验',
+          '熟练掌握JavaScript、TypeScript',
+          '熟悉Angular、React或Vue.js框架',
+          '具备良好的团队协作能力',
+          '本科及以上学历'
+        ]
+      };
+
+      // 调用豆包AI进行分析
+      const analysisResult = await this.doubaoAiService.analyzeResume(analysisRequest).toPromise();
+      
+      if (analysisResult) {
+        // 更新分析结果
+        this.updateAnalysisResults(analysisResult);
+        this.showAnalysisResults = true;
+        
+        this.snackBar.open('简历分析完成!', '查看结果', {
+          duration: 5000,
+          horizontalPosition: 'center',
+          verticalPosition: 'top'
+        });
+      }
+
+    } catch (error) {
+      console.error('简历分析失败:', error);
+      this.showUploadError('简历分析失败,请稍后重试');
+    } finally {
+      this.isAnalyzing = false;
+      this.analysisProgress = 100;
+    }
   }
 
   // 招聘阶段相关方法
@@ -902,6 +982,62 @@ export class Dashboard implements OnInit, AfterViewInit {
     // 这里可以打开试用期报告页面
   }
 
+  // 滑动功能相关属性和方法
+  currentScrollPosition = 0;
+  maxScrollPosition = 0;
+  scrollIndicatorDots: number[] = [];
+
+  initScrollIndicator(): void {
+    // 计算滚动指示器点数
+    const container = document.querySelector('.stages-timeline-container');
+    const timeline = document.querySelector('.stages-timeline');
+    
+    if (container && timeline) {
+      const containerHeight = container.clientHeight;
+      const timelineHeight = timeline.scrollHeight;
+      
+      if (timelineHeight > containerHeight) {
+        this.maxScrollPosition = timelineHeight - containerHeight;
+        const dotsCount = Math.ceil(timelineHeight / containerHeight);
+        this.scrollIndicatorDots = Array.from({ length: dotsCount }, (_, i) => i);
+      }
+    }
+  }
+
+  onTimelineScroll(event: Event): void {
+    const target = event.target as HTMLElement;
+    this.currentScrollPosition = target.scrollTop;
+    this.updateScrollIndicator();
+  }
+
+  updateScrollIndicator(): void {
+    const container = document.querySelector('.stages-timeline-container');
+    if (container) {
+      const scrollPercentage = this.currentScrollPosition / this.maxScrollPosition;
+      const activeIndex = Math.floor(scrollPercentage * this.scrollIndicatorDots.length);
+      
+      // 更新指示器状态
+      document.querySelectorAll('.scroll-dot').forEach((dot, index) => {
+        if (index <= activeIndex) {
+          dot.classList.add('active');
+        } else {
+          dot.classList.remove('active');
+        }
+      });
+    }
+  }
+
+  scrollToPosition(index: number): void {
+    const container = document.querySelector('.stages-timeline-container');
+    if (container) {
+      const scrollPosition = (index / this.scrollIndicatorDots.length) * this.maxScrollPosition;
+      container.scrollTo({
+        top: scrollPosition,
+        behavior: 'smooth'
+      });
+    }
+  }
+
   // 绩效筛选相关方法
   onDepartmentChange(event: any): void {
     console.log('部门筛选变更:', event.value);
@@ -939,24 +1075,100 @@ export class Dashboard implements OnInit, AfterViewInit {
   }
 
   private showUploadError(message: string) {
-    // 创建错误提示元素
-    const errorDiv = document.createElement('div');
-    errorDiv.className = 'upload-error-feedback';
-    errorDiv.innerHTML = `
-      <div class="error-content">
-        <mat-icon>error</mat-icon>
-        <span>${message}</span>
-      </div>
-    `;
-    
-    document.body.appendChild(errorDiv);
-    
-    // 3秒后移除
-    setTimeout(() => {
-      if (errorDiv.parentNode) {
-        errorDiv.parentNode.removeChild(errorDiv);
-      }
-    }, 3000);
+    this.snackBar.open(message, '关闭', {
+      duration: 3000,
+      panelClass: ['error-snackbar']
+    });
+  }
+
+  private updateAnalysisProgress(progress: number) {
+    this.analysisProgress = progress;
+  }
+
+  private updateAnalysisResults(response: ResumeAnalysisResponse) {
+    // 更新匹配维度
+    this.matchDimensions = response.matchDimensions.map(dim => ({
+      id: dim.id,
+      name: dim.name,
+      score: dim.score,
+      level: dim.score >= 80 ? 'high' : dim.score >= 60 ? 'medium' : 'low',
+      icon: this.getSkillIcon(dim.name)
+    }));
+
+    // 更新推荐结论
+    this.recommendation = {
+      title: response.recommendation.title,
+      level: response.recommendation.level,
+      levelText: this.getRecommendationLevelText(response.recommendation.level),
+      icon: this.getRecommendationIcon(response.recommendation.level),
+      summary: response.recommendation.summary,
+      reasons: response.recommendation.reasons,
+      concerns: response.recommendation.concerns
+    };
+
+    // 更新筛选信息
+    this.screeningInfo = response.screeningInfo.map(info => ({
+      id: info.id,
+      title: info.title,
+      detail: info.detail,
+      status: info.status,
+      statusText: this.getStatusText(info.status),
+      icon: this.getScreeningIcon(info.title)
+    }));
+
+    this.showAnalysisResults = true;
+  }
+
+  private getSkillIcon(skillName: string): string {
+    const iconMap: { [key: string]: string } = {
+      '建模经验': 'view_in_ar',
+      'UI设计': 'design_services',
+      '用户体验': 'psychology',
+      '团队协作': 'groups',
+      '项目管理': 'task_alt',
+      '技术能力': 'code',
+      '沟通能力': 'chat',
+      '学习能力': 'school'
+    };
+    return iconMap[skillName] || 'star';
+  }
+
+  private getRecommendationLevelText(level: string): string {
+    const levelMap: { [key: string]: string } = {
+      'recommend': '推荐',
+      'consider': '考虑',
+      'reject': '不推荐'
+    };
+    return levelMap[level] || '待定';
+  }
+
+  private getRecommendationIcon(level: string): string {
+    const iconMap: { [key: string]: string } = {
+      'recommend': 'thumb_up',
+      'consider': 'help',
+      'reject': 'thumb_down'
+    };
+    return iconMap[level] || 'help';
+  }
+
+  private getStatusText(status: string): string {
+    const statusMap: { [key: string]: string } = {
+      'pass': '符合',
+      'warning': '注意',
+      'fail': '不符合'
+    };
+    return statusMap[status] || '未知';
+  }
+
+  private getScreeningIcon(title: string): string {
+    const iconMap: { [key: string]: string } = {
+      '学历要求': 'school',
+      '工作经验': 'work',
+      '技能匹配': 'star',
+      '薪资期望': 'payments',
+      '到岗时间': 'schedule'
+    };
+    return iconMap[title] || 'info';
   }
 
   // 显示上传反馈
@@ -1053,8 +1265,225 @@ export class Dashboard implements OnInit, AfterViewInit {
 
   // 初始化图表(这里需要后续集成ECharts)
   private initCharts() {
-    // 图表初始化逻辑将在后续实现
-    console.log('初始化图表数据');
+    // 初始化离职原因图表
+    this.initResignationChart();
+  }
+
+  // 初始化离职原因图表
+  private initResignationChart() {
+    if (!this.resignationChartRef?.nativeElement) return;
+
+    const ctx = this.resignationChartRef.nativeElement.getContext('2d');
+    if (!ctx) return;
+
+    // 销毁现有图表
+    if (this.resignationChart) {
+      this.resignationChart.destroy();
+    }
+
+    const chartData = {
+      labels: this.resignationReasons.map(reason => reason.name),
+      datasets: [{
+        data: this.resignationReasons.map(reason => reason.percentage),
+        backgroundColor: [
+          '#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', 
+          '#FFEAA7', '#DDA0DD', '#98D8C8', '#F7DC6F'
+        ],
+        borderWidth: 2,
+        borderColor: '#fff'
+      }]
+    };
+
+    let config: ChartConfiguration;
+
+    // 通用动画配置
+    const animationConfig = {
+      duration: 800,
+      easing: 'easeInOutQuart' as const,
+      delay: (context: any) => context.dataIndex * 50
+    };
+
+    switch (this.reasonsChartType) {
+      case 'pie':
+        config = {
+          type: 'pie',
+          data: chartData,
+          options: {
+            responsive: true,
+            maintainAspectRatio: false,
+            animation: animationConfig,
+            plugins: {
+              legend: {
+                position: 'right',
+                labels: {
+                  usePointStyle: true,
+                  padding: 20,
+                  font: {
+                    size: 12
+                  }
+                }
+              },
+              tooltip: {
+                backgroundColor: 'rgba(0, 0, 0, 0.8)',
+                titleColor: '#fff',
+                bodyColor: '#fff',
+                borderColor: '#4ECDC4',
+                borderWidth: 1,
+                callbacks: {
+                  label: (context) => {
+                    const label = context.label || '';
+                    const value = context.parsed;
+                    const reason = this.resignationReasons[context.dataIndex];
+                    return `${label}: ${value}% (${reason.count}人)`;
+                  }
+                }
+              }
+            }
+          }
+        };
+        break;
+
+      case 'doughnut':
+        config = {
+          type: 'doughnut',
+          data: chartData,
+          options: {
+            responsive: true,
+            maintainAspectRatio: false,
+            animation: animationConfig,
+            plugins: {
+              legend: {
+                position: 'right',
+                labels: {
+                  usePointStyle: true,
+                  padding: 20,
+                  font: {
+                    size: 12
+                  }
+                }
+              },
+              tooltip: {
+                backgroundColor: 'rgba(0, 0, 0, 0.8)',
+                titleColor: '#fff',
+                bodyColor: '#fff',
+                borderColor: '#4ECDC4',
+                borderWidth: 1,
+                callbacks: {
+                  label: (context) => {
+                    const label = context.label || '';
+                    const value = context.parsed;
+                    const reason = this.resignationReasons[context.dataIndex];
+                    return `${label}: ${value}% (${reason.count}人)`;
+                  }
+                }
+              }
+            }
+          }
+        };
+        break;
+
+      case 'bar':
+        config = {
+          type: 'bar',
+          data: {
+            labels: this.resignationReasons.map(reason => reason.name),
+            datasets: [{
+              label: '离职占比 (%)',
+              data: this.resignationReasons.map(reason => reason.percentage),
+              backgroundColor: '#4ECDC4',
+              borderColor: '#45B7D1',
+              borderWidth: 1,
+              borderRadius: 4,
+              borderSkipped: false
+            }]
+          },
+          options: {
+            responsive: true,
+            maintainAspectRatio: false,
+            animation: {
+              duration: 800,
+              easing: 'easeInOutQuart' as const,
+              delay: (context: any) => context.dataIndex * 100
+            },
+            plugins: {
+              legend: {
+                display: false
+              },
+              tooltip: {
+                backgroundColor: 'rgba(0, 0, 0, 0.8)',
+                titleColor: '#fff',
+                bodyColor: '#fff',
+                borderColor: '#4ECDC4',
+                borderWidth: 1,
+                callbacks: {
+                  label: (context) => {
+                    const value = context.parsed.y;
+                    const reason = this.resignationReasons[context.dataIndex];
+                    return `${reason.name}: ${value}% (${reason.count}人)`;
+                  }
+                }
+              }
+            },
+            scales: {
+              y: {
+                beginAtZero: true,
+                max: 35,
+                grid: {
+                  color: 'rgba(0, 0, 0, 0.1)'
+                },
+                ticks: {
+                  font: {
+                    size: 11
+                  },
+                  callback: function(value) {
+                    return value + '%';
+                  }
+                }
+              },
+              x: {
+                grid: {
+                  display: false
+                },
+                ticks: {
+                  maxRotation: 45,
+                  minRotation: 0,
+                  font: {
+                    size: 11
+                  }
+                }
+              }
+            }
+          }
+        };
+        break;
+
+      default:
+        return;
+    }
+
+    this.resignationChart = new Chart(ctx, config);
+  }
+
+  // 切换图表类型 - 优化性能
+  onChartTypeChange() {
+    // 添加加载状态
+    const chartContainer = this.resignationChartRef?.nativeElement?.parentElement;
+    if (chartContainer) {
+      chartContainer.style.opacity = '0.7';
+      chartContainer.style.transition = 'opacity 0.3s ease';
+    }
+
+    // 使用 setTimeout 确保 UI 更新
+    setTimeout(() => {
+      this.initResignationChart();
+      
+      // 恢复透明度
+      if (chartContainer) {
+        setTimeout(() => {
+          chartContainer.style.opacity = '1';
+        }, 100);
+      }
+    }, 50);
   }
 
   // 拖拽排序
@@ -1103,6 +1532,17 @@ export class Dashboard implements OnInit, AfterViewInit {
     this.showTodoList = !this.showTodoList;
   }
 
+  // 悬浮待办事项面板相关属性和方法
+  isTodoPanelOpen: boolean = false;
+  
+  get todoCount(): number {
+    return this.todoList.length;
+  }
+
+  toggleTodoPanel(): void {
+    this.isTodoPanelOpen = !this.isTodoPanelOpen;
+  }
+
   // 获取甜甜圈图表特定选项
   private getDoughnutOptions(): any {
     return {
@@ -1115,6 +1555,7 @@ export class Dashboard implements OnInit, AfterViewInit {
     this.initPieChart();
     this.initLineChart();
     this.initRadarChart();
+    this.initResignationChart();
   }
 
   // 初始化职级分布饼图
@@ -1316,12 +1757,169 @@ export class Dashboard implements OnInit, AfterViewInit {
 
   // 拖拽排序功能
   onTodoDrop(event: CdkDragDrop<TodoItem[]>): void {
-    moveItemInArray(this.todoItems, event.previousIndex, event.currentIndex);
+    if (event.previousIndex !== event.currentIndex) {
+      moveItemInArray(this.todoItems, event.previousIndex, event.currentIndex);
+      this.showTodoDragFeedback();
+    }
   }
 
   // 更新待办事项状态
   updateTodoStatus(todo: TodoItem, status: 'pending' | 'completed' | 'in_progress'): void {
+    const oldStatus = todo.status;
     todo.status = status;
+    this.showTodoStatusFeedback(todo.title, oldStatus, status);
+  }
+
+  private showTodoDragFeedback(): void {
+    // 创建反馈元素
+    const feedback = document.createElement('div');
+    feedback.className = 'ios-drag-feedback';
+    feedback.innerHTML = `
+      <div class="ios-feedback-content">
+        <mat-icon>swap_vert</mat-icon>
+        <span>任务顺序已更新</span>
+      </div>
+    `;
+    
+    // 添加样式
+    feedback.style.cssText = `
+      position: fixed;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+      background: rgba(0, 122, 255, 0.95);
+      color: white;
+      padding: 12px 20px;
+      border-radius: 12px;
+      box-shadow: 0 8px 32px rgba(0, 122, 255, 0.3);
+      z-index: 10000;
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      font-size: 14px;
+      font-weight: 500;
+      backdrop-filter: blur(20px);
+      animation: iosFeedbackIn 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
+    `;
+
+    document.body.appendChild(feedback);
+
+    // 2秒后移除
+    setTimeout(() => {
+      feedback.style.animation = 'iosFeedbackOut 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)';
+      setTimeout(() => {
+        if (feedback.parentNode) {
+          feedback.parentNode.removeChild(feedback);
+        }
+      }, 300);
+    }, 2000);
+  }
+
+  private showTodoStatusFeedback(title: string, oldStatus: string, newStatus: string): void {
+    const statusMap = {
+      'pending': '待处理',
+      'in_progress': '进行中',
+      'completed': '已完成'
+    };
+
+    const iconMap = {
+      'pending': 'schedule',
+      'in_progress': 'play_circle',
+      'completed': 'check_circle'
+    };
+
+    const colorMap = {
+      'pending': '#8E8E93',
+      'in_progress': '#FF9500',
+      'completed': '#34C759'
+    };
+
+    // 创建反馈元素
+    const feedback = document.createElement('div');
+    feedback.className = 'ios-status-feedback';
+    feedback.innerHTML = `
+      <div class="ios-feedback-content">
+        <mat-icon style="color: ${colorMap[newStatus as keyof typeof colorMap]}">${iconMap[newStatus as keyof typeof iconMap]}</mat-icon>
+        <div class="ios-feedback-text">
+          <div class="ios-feedback-title">${title}</div>
+          <div class="ios-feedback-subtitle">${statusMap[oldStatus as keyof typeof statusMap]} → ${statusMap[newStatus as keyof typeof statusMap]}</div>
+        </div>
+      </div>
+    `;
+    
+    // 添加样式
+    feedback.style.cssText = `
+      position: fixed;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+      background: rgba(255, 255, 255, 0.95);
+      color: #1D1D1F;
+      padding: 16px 20px;
+      border-radius: 16px;
+      box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
+      z-index: 10000;
+      backdrop-filter: blur(20px);
+      animation: iosFeedbackIn 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
+      min-width: 280px;
+    `;
+
+    // 添加内部样式
+    const style = document.createElement('style');
+    style.textContent = `
+      .ios-feedback-content {
+        display: flex;
+        align-items: center;
+        gap: 12px;
+      }
+      .ios-feedback-text {
+        flex: 1;
+      }
+      .ios-feedback-title {
+        font-size: 16px;
+        font-weight: 600;
+        margin-bottom: 4px;
+      }
+      .ios-feedback-subtitle {
+        font-size: 14px;
+        color: #8E8E93;
+      }
+      @keyframes iosFeedbackIn {
+        from {
+          opacity: 0;
+          transform: translate(-50%, -50%) scale(0.8);
+        }
+        to {
+          opacity: 1;
+          transform: translate(-50%, -50%) scale(1);
+        }
+      }
+      @keyframes iosFeedbackOut {
+        from {
+          opacity: 1;
+          transform: translate(-50%, -50%) scale(1);
+        }
+        to {
+          opacity: 0;
+          transform: translate(-50%, -50%) scale(0.8);
+        }
+      }
+    `;
+    document.head.appendChild(style);
+    document.body.appendChild(feedback);
+
+    // 2.5秒后移除
+    setTimeout(() => {
+      feedback.style.animation = 'iosFeedbackOut 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)';
+      setTimeout(() => {
+        if (feedback.parentNode) {
+          feedback.parentNode.removeChild(feedback);
+        }
+        if (style.parentNode) {
+          style.parentNode.removeChild(style);
+        }
+      }, 300);
+    }, 2500);
   }
 
   // 处理按钮按压效果
@@ -1815,19 +2413,232 @@ export class Dashboard implements OnInit, AfterViewInit {
   viewReasonDetails(reasonId: string): void {
     const reason = this.resignationReasons.find(r => r.id === reasonId);
     if (reason) {
-      console.log('查看离职原因详情:', reason);
-      // 这里可以打开详情弹窗显示更多信息
+      this.selectedReason = reason;
+      this.selectedDetailAnalysis = this.getDetailAnalysis(reasonId);
+      this.selectedImprovementPlan = this.getImprovementPlan(reasonId);
+      this.showDetailPanel = true;
     }
   }
 
   viewImprovementPlan(reasonId: string): void {
     const reason = this.resignationReasons.find(r => r.id === reasonId);
     if (reason) {
-      console.log('查看改进建议:', reason);
-      // 这里可以显示针对该离职原因的改进建议
+      this.selectedReason = reason;
+      this.selectedDetailAnalysis = this.getDetailAnalysis(reasonId);
+      this.selectedImprovementPlan = this.getImprovementPlan(reasonId);
+      this.showDetailPanel = true;
     }
   }
 
+  closeDetailPanel(): void {
+    this.showDetailPanel = false;
+    this.selectedReason = null;
+    this.selectedDetailAnalysis = null;
+    this.selectedImprovementPlan = null;
+  }
+
+  exportDetailReport(): void {
+    if (this.selectedReason) {
+      // 导出详细报告的逻辑
+      this.snackBar.open(`正在导出"${this.selectedReason.name}"的详细报告...`, '关闭', {
+        duration: 3000,
+        horizontalPosition: 'center',
+        verticalPosition: 'top'
+      });
+    }
+  }
+
+  private getDetailAnalysis(reasonId: string): DetailAnalysis {
+    // 根据不同的离职原因返回对应的详细分析数据
+    const analysisData: { [key: string]: DetailAnalysis } = {
+      'salary': {
+        overview: '薪资待遇问题是当前最主要的离职原因,占比28.5%。主要体现在基本薪资偏低、绩效奖金不透明、福利待遇缺乏竞争力等方面。',
+        keyFactors: ['基本薪资偏低', '绩效考核不透明', '福利待遇单一', '薪资调整机制缺失', '市场竞争力不足'],
+        impactAnalysis: {
+          shortTerm: ['优秀员工流失加速', '招聘成本增加', '团队士气下降', '工作效率降低'],
+          longTerm: ['人才竞争力下降', '企业声誉受损', '核心技能流失', '业务发展受阻']
+        },
+        relatedDepartments: ['人力资源部', '财务部', '各业务部门'],
+        timeDistribution: [
+          { period: '第一季度', count: 3, percentage: 21.4 },
+          { period: '第二季度', count: 4, percentage: 28.6 },
+          { period: '第三季度', count: 5, percentage: 35.7 },
+          { period: '第四季度', count: 2, percentage: 14.3 }
+        ]
+      },
+      'career': {
+        overview: '职业发展问题占比22.8%,主要反映在晋升通道不明确、技能培训不足、职业规划缺乏指导等方面。',
+        keyFactors: ['晋升通道狭窄', '培训机会有限', '职业规划缺失', '技能发展停滞', '内部流动性差'],
+        impactAnalysis: {
+          shortTerm: ['员工积极性下降', '学习动力不足', '创新能力减弱'],
+          longTerm: ['组织活力下降', '人才梯队断层', '竞争优势丧失']
+        },
+        relatedDepartments: ['人力资源部', '培训部', '各业务部门'],
+        timeDistribution: [
+          { period: '第一季度', count: 2, percentage: 18.2 },
+          { period: '第二季度', count: 3, percentage: 27.3 },
+          { period: '第三季度', count: 4, percentage: 36.4 },
+          { period: '第四季度', count: 2, percentage: 18.2 }
+        ]
+      },
+      'workload': {
+        overview: '工作压力问题占比18.3%,主要表现为工作量过大、工作时间过长、工作节奏过快等。',
+        keyFactors: ['工作量过大', '加班频繁', '工作节奏快', '压力管理缺失', '工作生活平衡差'],
+        impactAnalysis: {
+          shortTerm: ['员工疲劳度增加', '工作质量下降', '健康问题增多'],
+          longTerm: ['员工流失率上升', '企业形象受损', '可持续发展受阻']
+        },
+        relatedDepartments: ['人力资源部', '运营部', '项目管理部'],
+        timeDistribution: [
+          { period: '第一季度', count: 2, percentage: 22.2 },
+          { period: '第二季度', count: 3, percentage: 33.3 },
+          { period: '第三季度', count: 2, percentage: 22.2 },
+          { period: '第四季度', count: 2, percentage: 22.2 }
+        ]
+      }
+    };
+
+    return analysisData[reasonId] || {
+      overview: '暂无详细分析数据',
+      keyFactors: [],
+      impactAnalysis: { shortTerm: [], longTerm: [] },
+      relatedDepartments: [],
+      timeDistribution: []
+    };
+  }
+
+  private getImprovementPlan(reasonId: string): ImprovementPlan {
+    // 根据不同的离职原因返回对应的改进计划
+    const improvementPlans: { [key: string]: ImprovementPlan } = {
+      'salary': {
+        priority: 'high',
+        timeline: '3-6个月',
+        actions: [
+          {
+            title: '薪酬体系重构',
+            description: '建立科学的薪酬体系,包括基本薪资、绩效奖金、福利待遇等全面优化',
+            responsible: '人力资源部',
+            deadline: '2024-04-30',
+            status: 'in_progress'
+          },
+          {
+            title: '市场薪酬调研',
+            description: '定期进行市场薪酬调研,确保薪酬水平具有市场竞争力',
+            responsible: '人力资源部',
+            deadline: '2024-03-15',
+            status: 'pending'
+          },
+          {
+            title: '绩效考核优化',
+            description: '完善绩效考核体系,建立透明公正的绩效评估机制',
+            responsible: '人力资源部',
+            deadline: '2024-05-31',
+            status: 'pending'
+          }
+        ],
+        expectedOutcome: '通过薪酬体系优化,预计可降低因薪资问题导致的离职率15-20%,提升员工满意度和忠诚度。',
+        successMetrics: [
+          '离职率下降15-20%',
+          '员工满意度调查薪酬满意度提升至80%以上',
+          '关键岗位人才保留率提升至90%以上',
+          '新员工入职率提升10%'
+        ],
+        resources: {
+          budget: '200-300万元',
+          personnel: ['人力资源总监', '薪酬专员', '财务经理', '各部门主管'],
+          tools: ['薪酬管理系统', '绩效考核平台', '市场调研工具']
+        }
+      },
+      'career': {
+        priority: 'high',
+        timeline: '6-12个月',
+        actions: [
+          {
+            title: '职业发展通道设计',
+            description: '建立清晰的职业发展通道,包括技术路线和管理路线',
+            responsible: '人力资源部',
+            deadline: '2024-06-30',
+            status: 'pending'
+          },
+          {
+            title: '培训体系建设',
+            description: '建立完善的培训体系,包括新员工培训、技能提升培训、领导力培训等',
+            responsible: '培训部',
+            deadline: '2024-08-31',
+            status: 'pending'
+          },
+          {
+            title: '导师制度建立',
+            description: '建立导师制度,为员工提供职业发展指导和支持',
+            responsible: '人力资源部',
+            deadline: '2024-05-31',
+            status: 'pending'
+          }
+        ],
+        expectedOutcome: '通过职业发展体系建设,预计可降低因职业发展问题导致的离职率20-25%,提升员工成长满意度。',
+        successMetrics: [
+          '员工职业发展满意度提升至85%以上',
+          '内部晋升比例提升至60%以上',
+          '培训参与率达到95%以上',
+          '关键人才保留率提升至95%以上'
+        ],
+        resources: {
+          budget: '150-200万元',
+          personnel: ['人力资源总监', '培训经理', '各部门主管', '资深员工导师'],
+          tools: ['学习管理系统', '职业发展平台', '在线培训工具']
+        }
+      },
+      'workload': {
+        priority: 'medium',
+        timeline: '3-6个月',
+        actions: [
+          {
+            title: '工作量评估与优化',
+            description: '对各岗位工作量进行科学评估,合理分配工作任务',
+            responsible: '运营部',
+            deadline: '2024-04-30',
+            status: 'pending'
+          },
+          {
+            title: '工作流程优化',
+            description: '优化工作流程,提高工作效率,减少不必要的工作环节',
+            responsible: '项目管理部',
+            deadline: '2024-05-31',
+            status: 'pending'
+          },
+          {
+            title: '压力管理培训',
+            description: '开展压力管理培训,帮助员工更好地应对工作压力',
+            responsible: '人力资源部',
+            deadline: '2024-03-31',
+            status: 'pending'
+          }
+        ],
+        expectedOutcome: '通过工作压力管理优化,预计可降低因工作压力导致的离职率10-15%,提升员工工作满意度。',
+        successMetrics: [
+          '员工工作压力满意度提升至75%以上',
+          '平均加班时间减少20%',
+          '员工健康指标改善',
+          '工作效率提升15%'
+        ],
+        resources: {
+          budget: '50-100万元',
+          personnel: ['运营总监', '项目经理', '人力资源专员', '心理咨询师'],
+          tools: ['工作量管理系统', '项目管理工具', '健康管理平台']
+        }
+      }
+    };
+
+    return improvementPlans[reasonId] || {
+      priority: 'medium',
+      timeline: '待定',
+      actions: [],
+      expectedOutcome: '暂无改进计划',
+      successMetrics: [],
+      resources: { budget: '待评估', personnel: [], tools: [] }
+    };
+  }
+
   getMetricClass(value: string): string {
     const numValue = parseInt(value);
     if (numValue >= 90) return 'metric-excellent';

+ 478 - 0
src/app/pages/hr/dashboard/resignation-detail-panel.component.scss

@@ -0,0 +1,478 @@
+.resignation-detail-panel {
+  display: flex;
+  flex-direction: column;
+  height: 100vh;
+  background: #ffffff;
+  border-radius: 12px;
+  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
+  overflow: hidden;
+
+  .panel-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 24px;
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    color: white;
+
+    .header-content {
+      display: flex;
+      align-items: center;
+      gap: 16px;
+
+      .reason-icon {
+        font-size: 32px;
+        width: 32px;
+        height: 32px;
+      }
+
+      .header-text {
+        h2 {
+          margin: 0;
+          font-size: 24px;
+          font-weight: 600;
+        }
+
+        .category-chip {
+          display: inline-block;
+          background: rgba(255, 255, 255, 0.2);
+          padding: 4px 12px;
+          border-radius: 16px;
+          font-size: 12px;
+          margin-top: 4px;
+        }
+      }
+    }
+
+    button {
+      color: white;
+    }
+  }
+
+  .panel-content {
+    flex: 1;
+    overflow: auto;
+
+    ::ng-deep .mat-mdc-tab-group {
+      height: 100%;
+
+      .mat-mdc-tab-header {
+        border-bottom: 1px solid #e0e0e0;
+      }
+
+      .mat-mdc-tab-body-wrapper {
+        flex: 1;
+        overflow: auto;
+      }
+    }
+
+    .tab-content {
+      padding: 24px;
+    }
+
+    .stats-section {
+      display: grid;
+      grid-template-columns: repeat(3, 1fr);
+      gap: 16px;
+      margin-bottom: 32px;
+
+      .stat-card {
+        text-align: center;
+        padding: 20px;
+        background: #f8f9fa;
+        border-radius: 12px;
+        border: 1px solid #e9ecef;
+
+        .stat-value {
+          font-size: 28px;
+          font-weight: 700;
+          color: #2c3e50;
+          margin-bottom: 8px;
+
+          &.trend {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            gap: 4px;
+
+            &.trend-up {
+              color: #e74c3c;
+            }
+
+            &.trend-down {
+              color: #27ae60;
+            }
+
+            &.trend-stable {
+              color: #f39c12;
+            }
+
+            mat-icon {
+              font-size: 20px;
+              width: 20px;
+              height: 20px;
+            }
+          }
+        }
+
+        .stat-label {
+          font-size: 14px;
+          color: #6c757d;
+          font-weight: 500;
+        }
+      }
+    }
+
+    .analysis-section {
+      h3 {
+        color: #2c3e50;
+        font-size: 18px;
+        font-weight: 600;
+        margin: 24px 0 12px 0;
+        border-bottom: 2px solid #667eea;
+        padding-bottom: 8px;
+      }
+
+      .overview-text {
+        line-height: 1.6;
+        color: #495057;
+        margin-bottom: 24px;
+      }
+
+      .factors-list {
+        margin-bottom: 24px;
+
+        ::ng-deep mat-chip {
+          background: #e3f2fd;
+          color: #1976d2;
+        }
+      }
+
+      .impact-analysis {
+        display: grid;
+        grid-template-columns: 1fr 1fr;
+        gap: 24px;
+        margin-bottom: 24px;
+
+        .impact-section {
+          h4 {
+            color: #495057;
+            font-size: 16px;
+            margin-bottom: 12px;
+          }
+
+          ul {
+            list-style: none;
+            padding: 0;
+
+            li {
+              padding: 8px 0;
+              padding-left: 20px;
+              position: relative;
+              color: #6c757d;
+
+              &::before {
+                content: '•';
+                color: #667eea;
+                font-weight: bold;
+                position: absolute;
+                left: 0;
+              }
+            }
+          }
+        }
+      }
+
+      .time-distribution {
+        .period-item {
+          display: grid;
+          grid-template-columns: 100px 1fr 60px;
+          align-items: center;
+          gap: 16px;
+          margin-bottom: 12px;
+
+          .period-label {
+            font-weight: 500;
+            color: #495057;
+          }
+
+          .period-bar {
+            height: 8px;
+            background: #e9ecef;
+            border-radius: 4px;
+            overflow: hidden;
+
+            .bar-fill {
+              height: 100%;
+              background: linear-gradient(90deg, #667eea, #764ba2);
+              border-radius: 4px;
+            }
+          }
+
+          .period-count {
+            text-align: right;
+            font-weight: 500;
+            color: #6c757d;
+          }
+        }
+      }
+    }
+
+    .improvement-header {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      margin-bottom: 24px;
+      padding: 16px;
+      background: #f8f9fa;
+      border-radius: 8px;
+
+      .priority-badge {
+        padding: 6px 16px;
+        border-radius: 20px;
+        font-weight: 600;
+        font-size: 12px;
+
+        &.priority-high {
+          background: #ffebee;
+          color: #c62828;
+        }
+
+        &.priority-medium {
+          background: #fff3e0;
+          color: #ef6c00;
+        }
+
+        &.priority-low {
+          background: #e8f5e8;
+          color: #2e7d32;
+        }
+      }
+
+      .timeline {
+        color: #6c757d;
+        font-weight: 500;
+      }
+    }
+
+    .expected-outcome {
+      margin-bottom: 32px;
+
+      h3 {
+        color: #2c3e50;
+        font-size: 18px;
+        font-weight: 600;
+        margin-bottom: 12px;
+      }
+
+      p {
+        line-height: 1.6;
+        color: #495057;
+      }
+    }
+
+    .action-plan {
+      margin-bottom: 32px;
+
+      h3 {
+        color: #2c3e50;
+        font-size: 18px;
+        font-weight: 600;
+        margin-bottom: 16px;
+      }
+
+      .actions-list {
+        .action-item {
+          background: #f8f9fa;
+          border-radius: 8px;
+          padding: 20px;
+          margin-bottom: 16px;
+          border-left: 4px solid #667eea;
+
+          .action-header {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            margin-bottom: 12px;
+
+            h4 {
+              margin: 0;
+              color: #2c3e50;
+              font-size: 16px;
+            }
+
+            .action-status {
+              padding: 4px 12px;
+              border-radius: 12px;
+              font-size: 12px;
+              font-weight: 500;
+
+              &.status-pending {
+                background: #fff3cd;
+                color: #856404;
+              }
+
+              &.status-in_progress {
+                background: #d1ecf1;
+                color: #0c5460;
+              }
+
+              &.status-completed {
+                background: #d4edda;
+                color: #155724;
+              }
+            }
+          }
+
+          .action-description {
+            color: #6c757d;
+            line-height: 1.5;
+            margin-bottom: 12px;
+          }
+
+          .action-meta {
+            display: flex;
+            gap: 24px;
+            font-size: 14px;
+            color: #6c757d;
+
+            .responsible,
+            .deadline {
+              font-weight: 500;
+            }
+          }
+        }
+      }
+    }
+
+    .success-metrics {
+      margin-bottom: 32px;
+
+      h3 {
+        color: #2c3e50;
+        font-size: 18px;
+        font-weight: 600;
+        margin-bottom: 12px;
+      }
+
+      ul {
+        list-style: none;
+        padding: 0;
+
+        li {
+          padding: 8px 0;
+          padding-left: 24px;
+          position: relative;
+          color: #495057;
+
+          &::before {
+            content: '✓';
+            color: #27ae60;
+            font-weight: bold;
+            position: absolute;
+            left: 0;
+          }
+        }
+      }
+    }
+
+    .resources {
+      h3 {
+        color: #2c3e50;
+        font-size: 18px;
+        font-weight: 600;
+        margin-bottom: 16px;
+      }
+
+      .resource-grid {
+        display: grid;
+        grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+        gap: 20px;
+
+        .resource-item {
+          background: #f8f9fa;
+          padding: 16px;
+          border-radius: 8px;
+
+          h4 {
+            margin: 0 0 12px 0;
+            color: #495057;
+            font-size: 14px;
+            font-weight: 600;
+          }
+
+          p {
+            margin: 0;
+            color: #6c757d;
+          }
+
+          .personnel-list,
+          .tools-list {
+            display: flex;
+            flex-wrap: wrap;
+            gap: 8px;
+
+            ::ng-deep mat-chip {
+              background: #e3f2fd;
+              color: #1976d2;
+              font-size: 12px;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  .panel-footer {
+    display: flex;
+    align-items: center;
+    justify-content: flex-end;
+    gap: 12px;
+    padding: 20px 24px;
+    border-top: 1px solid #e0e0e0;
+    background: #fafafa;
+
+    button {
+      min-width: 100px;
+    }
+  }
+}
+
+@media (max-width: 768px) {
+  .resignation-detail-panel {
+    .panel-content {
+      .stats-section {
+        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;
+
+      button {
+        width: 100%;
+      }
+    }
+  }
+}

+ 291 - 0
src/app/pages/hr/dashboard/resignation-detail-panel.component.ts

@@ -0,0 +1,291 @@
+import { Component, Input, Output, EventEmitter } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { MatDialogModule } from '@angular/material/dialog';
+import { MatButtonModule } from '@angular/material/button';
+import { MatIconModule } from '@angular/material/icon';
+import { MatTabsModule } from '@angular/material/tabs';
+import { MatChipsModule } from '@angular/material/chips';
+import { MatProgressBarModule } from '@angular/material/progress-bar';
+import { MatCardModule } from '@angular/material/card';
+
+export interface ResignationReason {
+  id: string;
+  name: string;
+  category: string;
+  categoryName: string;
+  icon: string;
+  percentage: number;
+  count: number;
+  description: string;
+  trend: {
+    direction: 'up' | 'down' | 'stable';
+    value: number;
+  };
+}
+
+export interface DetailAnalysis {
+  overview: string;
+  keyFactors: string[];
+  impactAnalysis: {
+    shortTerm: string[];
+    longTerm: string[];
+  };
+  relatedDepartments: string[];
+  timeDistribution: {
+    period: string;
+    count: number;
+    percentage: number;
+  }[];
+}
+
+export interface ImprovementPlan {
+  priority: 'high' | 'medium' | 'low';
+  timeline: string;
+  actions: {
+    title: string;
+    description: string;
+    responsible: string;
+    deadline: string;
+    status: 'pending' | 'in_progress' | 'completed';
+  }[];
+  expectedOutcome: string;
+  successMetrics: string[];
+  resources: {
+    budget: string;
+    personnel: string[];
+    tools: string[];
+  };
+}
+
+@Component({
+  selector: 'app-resignation-detail-panel',
+  standalone: true,
+  imports: [
+    CommonModule,
+    MatDialogModule,
+    MatButtonModule,
+    MatIconModule,
+    MatTabsModule,
+    MatChipsModule,
+    MatProgressBarModule,
+    MatCardModule,
+   // 添加 NgFor 到 imports 数组
+  ],
+  template: `
+    <div class="resignation-detail-panel">
+      <div class="panel-header">
+        <div class="header-content">
+          <mat-icon class="reason-icon">{{ reason.icon }}</mat-icon>
+          <div class="header-text">
+            <h2>{{ reason.name }}</h2>
+            <span class="category-chip">{{ reason.categoryName }}</span>
+          </div>
+        </div>
+        <button mat-icon-button (click)="onClose()">
+          <mat-icon>close</mat-icon>
+        </button>
+      </div>
+
+      <div class="panel-content">
+        <mat-tab-group>
+          <mat-tab label="详细分析">
+            <div class="tab-content">
+              <!-- 基础统计 -->
+              <div class="stats-section">
+                <div class="stat-card">
+                  <div class="stat-value">{{ reason.percentage }}%</div>
+                  <div class="stat-label">占比</div>
+                </div>
+                <div class="stat-card">
+                  <div class="stat-value">{{ reason.count }}</div>
+                  <div class="stat-label">人数</div>
+                </div>
+                <div class="stat-card">
+                  <div class="stat-value trend" [class]="getTrendClass()">
+                    <mat-icon>{{ getTrendIcon() }}</mat-icon>
+                    {{ reason.trend.value }}%
+                  </div>
+                  <div class="stat-label">趋势</div>
+                </div>
+              </div>
+
+              <!-- 详细分析 -->
+              <div class="analysis-section">
+                <h3>分析概述</h3>
+                <p class="overview-text">{{ detailAnalysis.overview }}</p>
+
+                <h3>关键因素</h3>
+                <div class="factors-list">
+                  <mat-chip-set>
+                    @for (factor of detailAnalysis.keyFactors; track factor) {
+                      <mat-chip>{{ factor }}</mat-chip>
+                    }
+                  </mat-chip-set>
+                </div>
+
+                <h3>影响分析</h3>
+                <div class="impact-analysis">
+                  <div class="impact-section">
+                    <h4>短期影响</h4>
+                    <ul>
+                      @for (impact of detailAnalysis.impactAnalysis.shortTerm; track impact) {
+                        <li>{{ impact }}</li>
+                      }
+                    </ul>
+                  </div>
+                  <div class="impact-section">
+                    <h4>长期影响</h4>
+                    <ul>
+                      @for (impact of detailAnalysis.impactAnalysis.longTerm; track impact) {
+                        <li>{{ impact }}</li>
+                      }
+                    </ul>
+                  </div>
+                </div>
+
+                <h3>时间分布</h3>
+                <div class="time-distribution">
+                  @for (period of detailAnalysis.timeDistribution; track period.period) {
+                    <div class="period-item">
+                      <div class="period-label">{{ period.period }}</div>
+                      <div class="period-bar">
+                        <div class="bar-fill" [style.width.%]="period.percentage"></div>
+                      </div>
+                      <div class="period-count">{{ period.count }}人</div>
+                    </div>
+                  }
+                </div>
+              </div>
+            </div>
+          </mat-tab>
+
+          <mat-tab label="改进建议">
+            <div class="tab-content">
+              <div class="improvement-header">
+                <div class="priority-badge" [class]="'priority-' + improvementPlan.priority">
+                  {{ getPriorityText() }}
+                </div>
+                <div class="timeline">预计时间:{{ improvementPlan.timeline }}</div>
+              </div>
+
+              <div class="expected-outcome">
+                <h3>预期成果</h3>
+                <p>{{ improvementPlan.expectedOutcome }}</p>
+              </div>
+
+              <div class="action-plan">
+                <h3>行动计划</h3>
+                <div class="actions-list">
+                  @for (action of improvementPlan.actions; track action.title) {
+                    <div class="action-item">
+                      <div class="action-header">
+                        <h4>{{ action.title }}</h4>
+                        <div class="action-status" [class]="'status-' + action.status">
+                          {{ getStatusText(action.status) }}
+                        </div>
+                      </div>
+                      <p class="action-description">{{ action.description }}</p>
+                      <div class="action-meta">
+                        <span class="responsible">负责人:{{ action.responsible }}</span>
+                        <span class="deadline">截止时间:{{ action.deadline }}</span>
+                      </div>
+                    </div>
+                  }
+                </div>
+              </div>
+
+              <div class="success-metrics">
+                  <h3>成功指标</h3>
+                  <ul>
+                    @for (metric of improvementPlan.successMetrics; track metric) {
+                      <li>{{ metric }}</li>
+                    }
+                  </ul>
+                </div>
+
+              <div class="resources">
+                <h3>所需资源</h3>
+                <div class="resource-grid">
+                  <div class="resource-item">
+                    <h4>预算</h4>
+                    <p>{{ improvementPlan.resources.budget }}</p>
+                  </div>
+                  <div class="resource-item">
+                      <h4>人员</h4>
+                      <div class="personnel-list">
+                        @for (person of improvementPlan.resources.personnel; track person) {
+                          <mat-chip>{{ person }}</mat-chip>
+                        }
+                      </div>
+                    </div>
+                  <div class="resource-item">
+                      <h4>工具</h4>
+                      <div class="tools-list">
+                        @for (tool of improvementPlan.resources.tools; track tool) {
+                          <mat-chip>{{ tool }}</mat-chip>
+                        }
+                      </div>
+                    </div>
+                </div>
+              </div>
+            </div>
+          </mat-tab>
+        </mat-tab-group>
+      </div>
+
+      <div class="panel-footer">
+        <button mat-button (click)="onClose()">关闭</button>
+        <button mat-raised-button color="primary" (click)="onExport()">
+          <mat-icon>download</mat-icon>
+          导出报告
+        </button>
+      </div>
+    </div>
+  `,
+  styleUrls: ['./resignation-detail-panel.component.scss']
+})
+export class ResignationDetailPanelComponent {
+  @Input() reason!: ResignationReason;
+  @Input() detailAnalysis!: DetailAnalysis;
+  @Input() improvementPlan!: ImprovementPlan;
+  @Output() close = new EventEmitter<void>();
+  @Output() export = new EventEmitter<void>();
+
+  onClose(): void {
+    this.close.emit();
+  }
+
+  onExport(): void {
+    this.export.emit();
+  }
+
+  getTrendClass(): string {
+    return `trend-${this.reason.trend.direction}`;
+  }
+
+  getTrendIcon(): string {
+    switch (this.reason.trend.direction) {
+      case 'up': return 'trending_up';
+      case 'down': return 'trending_down';
+      default: return 'trending_flat';
+    }
+  }
+
+  getPriorityText(): string {
+    switch (this.improvementPlan.priority) {
+      case 'high': return '高优先级';
+      case 'medium': return '中优先级';
+      case 'low': return '低优先级';
+      default: return '未知';
+    }
+  }
+
+  getStatusText(status: string): string {
+    switch (status) {
+      case 'pending': return '待开始';
+      case 'in_progress': return '进行中';
+      case 'completed': return '已完成';
+      default: return '未知';
+    }
+  }
+}

+ 100 - 6
src/app/pages/hr/recruitment-performance/recruitment-performance.html

@@ -58,6 +58,24 @@
                 </div>
               }
 
+              @if (selectedResume()!.status === 'failed') {
+                <div class="failed-state">
+                  <mat-icon class="error-icon">error_outline</mat-icon>
+                  <h4>分析失败</h4>
+                  <p>简历分析过程中出现错误,请重试或检查文件格式</p>
+                  <div class="failed-actions">
+                    <button mat-raised-button color="primary" (click)="retryAnalysis(selectedResume()!.id)">
+                      <mat-icon>refresh</mat-icon>
+                      重新分析
+                    </button>
+                    <button mat-stroked-button color="warn" (click)="deleteAnalysis(selectedResume()!.id)">
+                      <mat-icon>delete</mat-icon>
+                      删除记录
+                    </button>
+                  </div>
+                </div>
+              }
+
               @if (selectedResume()!.status === 'completed' && selectedResume()!.analysisResult) {
                 <div class="completed-analysis">
                   <!-- 推荐结论 -->
@@ -121,8 +139,8 @@
               <h4>分析记录</h4>
               <div class="history-list">
                 @for (analysis of resumeAnalyses(); track analysis.id) {
-                  <div class="history-item" [class.selected]="selectedResume()?.id === analysis.id" (click)="selectedResume.set(analysis)">
-                    <div class="item-info">
+                  <div class="history-item" [class.selected]="selectedResume()?.id === analysis.id">
+                    <div class="item-info" (click)="selectedResume.set(analysis)">
                       <span class="file-name">{{ analysis.fileName }}</span>
                       <span class="upload-time">{{ analysis.uploadTime | date:'MM-dd HH:mm' }}</span>
                     </div>
@@ -134,7 +152,21 @@
                         <mat-icon class="status-icon completed">check_circle</mat-icon>
                       }
                       @if (analysis.status === 'failed') {
-                        <mat-icon class="status-icon failed">error</mat-icon>
+                        <div class="failed-status">
+                          <mat-icon class="status-icon failed">error</mat-icon>
+                          <div class="failed-actions">
+                            <button mat-icon-button color="primary" 
+                                    (click)="retryAnalysis(analysis.id); $event.stopPropagation()"
+                                    matTooltip="重新分析">
+                              <mat-icon>refresh</mat-icon>
+                            </button>
+                            <button mat-icon-button color="warn" 
+                                    (click)="deleteAnalysis(analysis.id); $event.stopPropagation()"
+                                    matTooltip="删除记录">
+                              <mat-icon>delete</mat-icon>
+                            </button>
+                          </div>
+                        </div>
                       }
                     </div>
                   </div>
@@ -217,9 +249,13 @@
 
           <!-- 阶段时间线 -->
           <div class="stages-timeline">
-            @for (stage of recruitmentStages(); track $index) {
-              <div class="timeline-item">
-                <div class="timeline-marker"></div>
+            @for (stage of recruitmentStages(); track $index; let i = $index) {
+              <div class="timeline-item" 
+                   [class]="getStageStatusClass(stage, i)"
+                   (click)="onStageClick(stage, i)">
+                <div class="timeline-marker">
+                  <mat-icon>{{ getStageIcon(stage.stage) }}</mat-icon>
+                </div>
                 <div class="timeline-content">
                   <div class="stage-header">
                     <h5>{{ stage.stage }}</h5>
@@ -232,9 +268,67 @@
                       <p><strong>备注:</strong>{{ stage.comments }}</p>
                     }
                   </div>
+                  @if (selectedStage() && selectedStage()?.evaluationTime === stage.evaluationTime) {
+                    <div class="stage-actions">
+                      <button mat-stroked-button color="primary" size="small">
+                        <mat-icon>edit</mat-icon>
+                        编辑
+                      </button>
+                      <button mat-stroked-button color="accent" size="small">
+                        <mat-icon>visibility</mat-icon>
+                        详情
+                      </button>
+                    </div>
+                  }
                 </div>
+                @if (!$last) {
+                  <div class="timeline-connector"></div>
+                }
               </div>
             }
+            
+            @if (recruitmentStages().length === 0) {
+              <div class="empty-timeline">
+                <mat-icon>timeline</mat-icon>
+                <p>暂无招聘流程记录,请添加第一个阶段</p>
+              </div>
+            }
+          </div>
+
+          <!-- 进度控制区域 -->
+          <div class="progress-controls">
+            <div class="progress-info">
+              <h4>流程进度控制</h4>
+              <div class="progress-stats">
+                <div class="stat-item">
+                  <span class="label">当前阶段:</span>
+                  <span class="value">{{ getCurrentStageStatus() }}</span>
+                </div>
+                <div class="stat-item">
+                  <span class="label">整体进度:</span>
+                  <span class="value">{{ getStageProgress() }}%</span>
+                </div>
+                @if (canProceedToNext()) {
+                  <div class="stat-item">
+                    <span class="label">下一阶段:</span>
+                    <span class="value">{{ getNextStageName() }}</span>
+                  </div>
+                }
+              </div>
+            </div>
+            
+            <div class="progress-actions">
+              @if (canProceedToNext()) {
+                <button mat-raised-button color="primary" (click)="proceedToNextStage()">
+                  <mat-icon>arrow_forward</mat-icon>
+                  进入下一阶段
+                </button>
+              }
+              <button mat-stroked-button color="warn" (click)="resetStageProgress()">
+                <mat-icon>refresh</mat-icon>
+                重置进度
+              </button>
+            </div>
           </div>
 
           <!-- 添加新阶段 -->

+ 245 - 4
src/app/pages/hr/recruitment-performance/recruitment-performance.scss

@@ -32,11 +32,11 @@
         
         mat-icon {
           margin-right: 6px;
-        }
+      
         
         &:hover {
           background: rgba(0, 122, 255, 0.08);
-        }
+      
         
         &:active {
           transform: scale(0.97);
@@ -781,6 +781,40 @@
           }
         }
         
+        .failed-state {
+          text-align: center;
+          padding: 48px 24px;
+          
+          .error-icon {
+            font-size: 48px;
+            color: #f44336;
+            margin-bottom: 16px;
+          }
+          
+          h4 {
+            margin: 0 0 12px 0;
+            color: #f44336;
+            font-weight: 600;
+          }
+          
+          p {
+            color: #666;
+            font-size: 16px;
+            margin-bottom: 24px;
+            line-height: 1.6;
+          }
+          
+          .failed-actions {
+            display: flex;
+            gap: 16px;
+            justify-content: center;
+            
+            button {
+              min-width: 120px;
+            }
+          }
+        }
+        
         .completed-analysis {
           .recommendation-section {
             margin-bottom: 32px;
@@ -988,6 +1022,35 @@
                   color: #f44336;
                 }
               }
+              
+              .failed-status {
+                display: flex;
+                align-items: center;
+                gap: 8px;
+                
+                .failed-actions {
+                  display: flex;
+                  gap: 4px;
+                  opacity: 0;
+                  transition: opacity 0.3s ease;
+                  
+                  button {
+                    width: 32px;
+                    height: 32px;
+                    line-height: 32px;
+                    
+                    mat-icon {
+                      font-size: 16px;
+                      width: 16px;
+                      height: 16px;
+                    }
+                  }
+                }
+              }
+            }
+            
+            &:hover .failed-status .failed-actions {
+              opacity: 1;
             }
           }
         }
@@ -1027,15 +1090,82 @@
         .timeline-item {
           display: flex;
           margin-bottom: 24px;
+          padding: 16px;
+          border-radius: 12px;
+          cursor: pointer;
+          transition: all 0.3s ease;
+          position: relative;
+          
+          &:hover {
+            background: rgba(102, 126, 234, 0.05);
+            transform: translateX(4px);
+          }
+          
+          &.selected {
+            background: rgba(102, 126, 234, 0.1);
+            border: 2px solid #667eea;
+            box-shadow: 0 4px 12px rgba(102, 126, 234, 0.2);
+          }
+          
+          &.current {
+            border-left: 4px solid #667eea;
+          }
+          
+          &.通过 {
+            border-left-color: #4CAF50;
+            
+            .timeline-marker {
+              background: #4CAF50;
+              color: white;
+            }
+          }
+          
+          &.不通过 {
+            border-left-color: #f44336;
+            
+            .timeline-marker {
+              background: #f44336;
+              color: white;
+            }
+          }
+          
+          &.待定 {
+            border-left-color: #ff9800;
+            
+            .timeline-marker {
+              background: #ff9800;
+              color: white;
+            }
+          }
           
           .timeline-marker {
-            width: 16px;
-            height: 16px;
+            width: 40px;
+            height: 40px;
             border-radius: 50%;
             background: #667eea;
             margin-right: 16px;
             margin-top: 4px;
             flex-shrink: 0;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            color: white;
+            
+            mat-icon {
+              font-size: 20px;
+              width: 20px;
+              height: 20px;
+            }
+          }
+          
+          .timeline-connector {
+            position: absolute;
+            left: 36px;
+            top: 60px;
+            width: 2px;
+            height: 40px;
+            background: #e0e0e0;
+            z-index: 1;
           }
           
           .timeline-content {
@@ -1085,6 +1215,117 @@
                 margin: 4px 0;
               }
             }
+            
+            .stage-actions {
+              margin-top: 12px;
+              display: flex;
+              gap: 8px;
+              opacity: 0;
+              transition: opacity 0.3s ease;
+              
+              button {
+                height: 32px;
+                line-height: 32px;
+                font-size: 12px;
+                
+                mat-icon {
+                  font-size: 16px;
+                  width: 16px;
+                  height: 16px;
+                  margin-right: 4px;
+                }
+              }
+            }
+          }
+          
+          &.selected .stage-actions {
+            opacity: 1;
+          }
+        }
+        
+        .empty-timeline {
+          text-align: center;
+          padding: 48px 24px;
+          color: #999;
+          
+          mat-icon {
+            font-size: 48px;
+            width: 48px;
+            height: 48px;
+            margin-bottom: 16px;
+            color: #ccc;
+          }
+          
+          p {
+            font-size: 16px;
+            margin: 0;
+          }
+        }
+      }
+      
+      .progress-controls {
+        margin-top: 32px;
+        padding: 24px;
+        background: linear-gradient(135deg, rgba(102, 126, 234, 0.05) 0%, rgba(118, 75, 162, 0.05) 100%);
+        border-radius: 16px;
+        border: 1px solid rgba(102, 126, 234, 0.1);
+        
+        .progress-info {
+          margin-bottom: 20px;
+          
+          h4 {
+            margin: 0 0 16px 0;
+            color: #333;
+            font-weight: 600;
+            display: flex;
+            align-items: center;
+            gap: 8px;
+            
+            &::before {
+              content: '';
+              width: 4px;
+              height: 20px;
+              background: linear-gradient(135deg, #667eea, #764ba2);
+              border-radius: 2px;
+            }
+          }
+          
+          .progress-stats {
+            display: grid;
+            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+            gap: 16px;
+            
+            .stat-item {
+              display: flex;
+              justify-content: space-between;
+              align-items: center;
+              padding: 12px 16px;
+              background: white;
+              border-radius: 8px;
+              border: 1px solid rgba(0, 0, 0, 0.1);
+              
+              .label {
+                font-weight: 500;
+                color: #666;
+              }
+              
+              .value {
+                font-weight: 600;
+                color: #333;
+              }
+            }
+          }
+        }
+        
+        .progress-actions {
+          display: flex;
+          gap: 12px;
+          justify-content: flex-end;
+          
+          button {
+            min-width: 140px;
+          }
+        }
           }
         }
       }

+ 250 - 23
src/app/pages/hr/recruitment-performance/recruitment-performance.ts

@@ -93,6 +93,9 @@ export class RecruitmentPerformanceComponent implements AfterViewInit, OnDestroy
   
   // 招聘流程跟踪
   recruitmentStages = signal<RecruitmentStage[]>([]);
+  selectedStage = signal<RecruitmentStage | null>(null);
+  stageProgress = signal<number>(0);
+  currentStageIndex = signal<number>(0);
   newStage = {
     stage: '简历初筛',
     result: '通过',
@@ -100,6 +103,14 @@ export class RecruitmentPerformanceComponent implements AfterViewInit, OnDestroy
     comments: ''
   };
 
+  // 四个标准阶段定义
+  standardStages = [
+    { name: '简历初筛', icon: 'description', description: '筛选符合基本要求的简历' },
+    { name: '面试评估', icon: 'person', description: '技能和文化匹配度评估' },
+    { name: '入职评定', icon: 'how_to_reg', description: '最终录用决定和入职准备' },
+    { name: '试用期跟踪', icon: 'trending_up', description: '试用期表现跟踪和评估' }
+  ];
+
   // 招聘四阶段定义
   recruitmentPhases = [
     {
@@ -454,6 +465,19 @@ export class RecruitmentPerformanceComponent implements AfterViewInit, OnDestroy
   onResumeUpload(event: any) {
     const file = event.target.files[0];
     if (file) {
+      // 验证文件类型
+      const allowedTypes = ['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'];
+      if (!allowedTypes.includes(file.type)) {
+        alert('不支持的文件格式,请上传PDF、DOC或DOCX文件');
+        return;
+      }
+
+      // 验证文件大小 (10MB)
+      if (file.size > 10 * 1024 * 1024) {
+        alert('文件大小超过10MB限制');
+        return;
+      }
+
       const newAnalysis: ResumeAnalysis = {
         id: Date.now().toString(),
         fileName: file.name,
@@ -462,39 +486,149 @@ export class RecruitmentPerformanceComponent implements AfterViewInit, OnDestroy
       };
       
       this.resumeAnalyses.update(analyses => [...analyses, newAnalysis]);
+      this.selectedResume.set(newAnalysis);
       
       // 模拟AI分析过程
-      setTimeout(() => {
-        this.simulateAIAnalysis(newAnalysis.id);
-      }, 3000);
+      this.simulateAIAnalysis(newAnalysis.id);
     }
   }
 
-  private simulateAIAnalysis(analysisId: string) {
+  private simulateAIAnalysis(analysisId: string, retryCount: number = 0) {
+    const maxRetries = 2;
+    const analysisDelay = 3000 + (retryCount * 1000); // 重试时延长等待时间
+
+    setTimeout(() => {
+      // 模拟分析成功率(80%成功率,前两次可能失败)
+      const shouldSucceed = retryCount >= 1 || Math.random() > 0.2;
+      
+      if (shouldSucceed) {
+        // 分析成功
+        this.resumeAnalyses.update(analyses => 
+          analyses.map(analysis => 
+            analysis.id === analysisId 
+              ? {
+                  ...analysis,
+                  status: 'completed' as const,
+                  analysisResult: {
+                    recommendation: this.getRandomRecommendation(),
+                    reason: this.getRandomAnalysisReason(),
+                    coreSkills: this.generateRandomSkills(),
+                    screeningInfo: {
+                      education: this.getRandomEducation(),
+                      workYears: this.getRandomWorkYears(),
+                      coreSkills: this.getRandomCoreSkills()
+                    }
+                  }
+                }
+              : analysis
+          )
+        );
+      } else {
+        // 分析失败
+        if (retryCount < maxRetries) {
+          // 标记为失败状态,但准备重试
+          this.resumeAnalyses.update(analyses => 
+            analyses.map(analysis => 
+              analysis.id === analysisId 
+                ? { ...analysis, status: 'failed' as const }
+                : analysis
+            )
+          );
+          
+          // 1秒后自动重试
+          setTimeout(() => {
+            this.resumeAnalyses.update(analyses => 
+              analyses.map(analysis => 
+                analysis.id === analysisId 
+                  ? { ...analysis, status: 'processing' as const }
+                  : analysis
+              )
+            );
+            this.simulateAIAnalysis(analysisId, retryCount + 1);
+          }, 1000);
+        } else {
+          // 最终失败
+          this.resumeAnalyses.update(analyses => 
+            analyses.map(analysis => 
+              analysis.id === analysisId 
+                ? { ...analysis, status: 'failed' as const }
+                : analysis
+            )
+          );
+        }
+      }
+    }, analysisDelay);
+  }
+
+  // 重新分析失败的简历
+  retryAnalysis(analysisId: string) {
     this.resumeAnalyses.update(analyses => 
       analyses.map(analysis => 
         analysis.id === analysisId 
-          ? {
-              ...analysis,
-              status: 'completed' as const,
-              analysisResult: {
-                recommendation: '推荐' as const,
-                reason: '候选人技能匹配度较高,具备相关工作经验',
-                coreSkills: [
-                  { name: '专业技能', score: 85, matched: true },
-                  { name: '工作经验', score: 78, matched: true },
-                  { name: '学历背景', score: 90, matched: true }
-                ],
-                screeningInfo: {
-                  education: '本科',
-                  workYears: '2-3年',
-                  coreSkills: ['专业技能', '沟通能力']
-                }
-              }
-            }
+          ? { ...analysis, status: 'processing' as const, analysisResult: undefined }
           : analysis
       )
     );
+    this.simulateAIAnalysis(analysisId, 0);
+  }
+
+  // 删除分析记录
+  deleteAnalysis(analysisId: string) {
+    this.resumeAnalyses.update(analyses => 
+      analyses.filter(analysis => analysis.id !== analysisId)
+    );
+    
+    // 如果删除的是当前选中的分析,清空选择
+    if (this.selectedResume()?.id === analysisId) {
+      this.selectedResume.set(null);
+    }
+  }
+
+  // 辅助方法:生成随机推荐结果
+  private getRandomRecommendation(): '推荐' | '不推荐' | '待定' {
+    const recommendations: ('推荐' | '不推荐' | '待定')[] = ['推荐', '不推荐', '待定'];
+    return recommendations[Math.floor(Math.random() * recommendations.length)];
+  }
+
+  // 辅助方法:生成随机分析原因
+  private getRandomAnalysisReason(): string {
+    const reasons = [
+      '候选人技能匹配度较高,具备相关工作经验',
+      '候选人经验丰富,但部分技能需要进一步评估',
+      '候选人基础技能良好,但缺乏相关行业经验',
+      '候选人学历背景优秀,技能匹配度中等',
+      '候选人综合素质良好,建议进入面试环节'
+    ];
+    return reasons[Math.floor(Math.random() * reasons.length)];
+  }
+
+  // 辅助方法:生成随机技能评分
+  private generateRandomSkills() {
+    const skillNames = ['专业技能', '工作经验', '学历背景', '沟通能力', '团队协作'];
+    return skillNames.map(name => ({
+      name,
+      score: Math.floor(Math.random() * 40) + 60, // 60-100分
+      matched: Math.random() > 0.3 // 70%匹配率
+    }));
+  }
+
+  // 辅助方法:生成随机学历
+  private getRandomEducation(): string {
+    const educations = ['专科', '本科', '硕士', '博士'];
+    return educations[Math.floor(Math.random() * educations.length)];
+  }
+
+  // 辅助方法:生成随机工作年限
+  private getRandomWorkYears(): string {
+    const workYears = ['1年以下', '1-2年', '2-3年', '3-5年', '5年以上'];
+    return workYears[Math.floor(Math.random() * workYears.length)];
+  }
+
+  // 辅助方法:生成随机核心技能
+  private getRandomCoreSkills(): string[] {
+    const allSkills = ['专业技能', '沟通能力', '团队协作', '项目管理', '创新思维', '学习能力'];
+    const skillCount = Math.floor(Math.random() * 3) + 2; // 2-4个技能
+    return allSkills.slice(0, skillCount);
   }
 
   // 添加招聘阶段
@@ -530,7 +664,100 @@ export class RecruitmentPerformanceComponent implements AfterViewInit, OnDestroy
   getStageProgress(): number {
     const stages = this.recruitmentStages();
     const totalStages = 4; // 简历初筛、面试评估、入职评定、试用期跟踪
-    return Math.round((stages.length / totalStages) * 100);
+    const progress = Math.round((stages.length / totalStages) * 100);
+    this.stageProgress.set(progress);
+    return progress;
+  }
+
+  // 阶段点击处理
+  onStageClick(stage: RecruitmentStage, index: number): void {
+    this.selectedStage.set(stage);
+    this.currentStageIndex.set(index);
+    console.log('选中阶段:', stage);
+  }
+
+  // 获取阶段状态类
+  getStageStatusClass(stage: RecruitmentStage, index: number): string {
+    const currentIndex = this.currentStageIndex();
+    const selectedStage = this.selectedStage();
+    
+    let statusClass = stage.result.toLowerCase();
+    
+    if (selectedStage && selectedStage.evaluationTime === stage.evaluationTime) {
+      statusClass += ' selected';
+    }
+    
+    if (index === currentIndex) {
+      statusClass += ' current';
+    }
+    
+    return statusClass;
+  }
+
+  // 进入下一阶段
+  proceedToNextStage(): void {
+    const currentIndex = this.currentStageIndex();
+    const stages = this.recruitmentStages();
+    
+    if (currentIndex < this.standardStages.length - 1) {
+      const nextStage = this.standardStages[currentIndex + 1];
+      this.newStage.stage = nextStage.name;
+      this.currentStageIndex.set(currentIndex + 1);
+      
+      // 自动填充下一阶段信息
+      this.newStage.result = '待定';
+      this.newStage.evaluator = '';
+      this.newStage.comments = `准备进入${nextStage.name}阶段`;
+    }
+  }
+
+  // 重置阶段进度
+  resetStageProgress(): void {
+    this.recruitmentStages.set([]);
+    this.selectedStage.set(null);
+    this.currentStageIndex.set(0);
+    this.stageProgress.set(0);
+    this.newStage = {
+      stage: '简历初筛',
+      result: '通过',
+      evaluator: '',
+      comments: ''
+    };
+  }
+
+  // 获取阶段图标
+  getStageIcon(stageName: string): string {
+    const stage = this.standardStages.find(s => s.name === stageName);
+    return stage ? stage.icon : 'help';
+  }
+
+  // 获取阶段描述
+  getStageDescription(stageName: string): string {
+    const stage = this.standardStages.find(s => s.name === stageName);
+    return stage ? stage.description : '';
+  }
+
+  // 检查是否可以进入下一阶段
+  canProceedToNext(): boolean {
+    const currentIndex = this.currentStageIndex();
+    const stages = this.recruitmentStages();
+    
+    // 检查当前阶段是否已完成且结果为通过
+    if (stages.length > currentIndex) {
+      const currentStage = stages[currentIndex];
+      return currentStage.result === '通过';
+    }
+    
+    return false;
+  }
+
+  // 获取下一阶段名称
+  getNextStageName(): string {
+    const currentIndex = this.currentStageIndex();
+    if (currentIndex < this.standardStages.length - 1) {
+      return this.standardStages[currentIndex + 1].name;
+    }
+    return '流程完成';
   }
 
   // 计算各阶段通过率

+ 34 - 0
src/app/pages/hr/shared/hr-common.scss

@@ -1,5 +1,39 @@
 /* HR板块统一样式文件 */
 
+/* 全局下拉面板样式修复 - 适用于整个HR板块 */
+::ng-deep .mat-mdc-select-panel {
+  background: rgba(255, 255, 255, 0.98) !important;
+  backdrop-filter: blur(10px) !important;
+  border-radius: 12px !important;
+  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15) !important;
+  border: 1px solid rgba(255, 255, 255, 0.2) !important;
+  max-height: 300px !important;
+  
+  .mat-mdc-option {
+    background: transparent !important;
+    color: #333 !important;
+    font-size: 16px !important;
+    padding: 12px 16px !important;
+    border-bottom: 1px solid rgba(0, 0, 0, 0.05) !important;
+    transition: all 0.2s ease !important;
+    
+    &:hover {
+      background: rgba(30, 64, 175, 0.1) !important;
+      color: #1e40af !important;
+    }
+    
+    &.mdc-list-item--selected {
+      background: rgba(30, 64, 175, 0.15) !important;
+      color: #1e40af !important;
+      font-weight: 600 !important;
+    }
+    
+    &:last-child {
+      border-bottom: none !important;
+    }
+  }
+}
+
 /* 主题色彩变量 */
 $hr-primary: #1e40af;
 $hr-primary-light: #3b82f6;

+ 274 - 0
src/app/pages/shared/dropdown/dropdown-demo.component.ts

@@ -0,0 +1,274 @@
+import { Component } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { DropdownComponent, DropdownOption } from './dropdown.component';
+
+@Component({
+  selector: 'app-dropdown-demo',
+  standalone: true,
+  imports: [CommonModule, FormsModule, DropdownComponent],
+  template: `
+    <div class="demo-container">
+      <h1>下拉列表组件演示</h1>
+      
+      <div class="demo-section">
+        <h2>基础下拉列表</h2>
+        <app-dropdown
+          [options]="basicOptions"
+          placeholder="请选择一个选项"
+          label="基础选择"
+          [(ngModel)]="selectedBasic"
+          (selectionChange)="onBasicChange($event)">
+        </app-dropdown>
+        <p>选中值: {{ selectedBasic || '无' }}</p>
+      </div>
+
+      <div class="demo-section">
+        <h2>多选下拉列表</h2>
+        <app-dropdown
+          [options]="basicOptions"
+          [multiple]="true"
+          placeholder="请选择多个选项"
+          label="多选"
+          [(ngModel)]="selectedMultiple"
+          (selectionChange)="onMultipleChange($event)">
+        </app-dropdown>
+        <p>选中值: {{ selectedMultiple.join(', ') || '无' }}</p>
+      </div>
+
+      <div class="demo-section">
+        <h2>可搜索下拉列表</h2>
+        <app-dropdown
+          [options]="searchableOptions"
+          [searchable]="true"
+          placeholder="搜索并选择"
+          label="可搜索"
+          helpText="输入关键词快速查找选项,支持键盘导航"
+          [(ngModel)]="selectedSearchable"
+          (selectionChange)="onSearchableChange($event)">
+        </app-dropdown>
+        <p>选中值: {{ selectedSearchable || '无' }}</p>
+      </div>
+
+      <div class="demo-section">
+        <h2>带图标的下拉列表</h2>
+        <app-dropdown
+          [options]="iconOptions"
+          placeholder="选择带图标的选项"
+          label="图标选择"
+          [(ngModel)]="selectedIcon"
+          (selectionChange)="onIconChange($event)">
+        </app-dropdown>
+        <p>选中值: {{ selectedIcon || '无' }}</p>
+      </div>
+
+      <div class="demo-section">
+        <h2>分组下拉列表</h2>
+        <app-dropdown
+          [options]="groupedOptions"
+          [searchable]="true"
+          placeholder="选择分组选项"
+          label="分组选择"
+          [(ngModel)]="selectedGrouped"
+          (selectionChange)="onGroupedChange($event)">
+        </app-dropdown>
+        <p>选中值: {{ selectedGrouped || '无' }}</p>
+      </div>
+
+      <div class="demo-section">
+        <h2>禁用状态</h2>
+        <app-dropdown
+          [options]="basicOptions"
+          [disabled]="true"
+          placeholder="禁用状态"
+          label="禁用选择"
+          [(ngModel)]="selectedDisabled">
+        </app-dropdown>
+      </div>
+
+      <div class="demo-section">
+        <h2>无障碍功能演示</h2>
+        <app-dropdown
+          label="无障碍功能演示"
+          [options]="accessibilityOptions"
+          [searchable]="true"
+          placeholder="支持键盘导航和屏幕阅读器"
+          helpText="使用Tab键聚焦,空格或回车键打开,方向键导航,Escape键关闭"
+          [(ngModel)]="selectedAccessibility"
+          (selectionChange)="onSelectionChange('accessibility', $event)">
+        </app-dropdown>
+      </div>
+
+      <div class="demo-section">
+        <h2>错误状态</h2>
+        <app-dropdown
+          [options]="basicOptions"
+          placeholder="错误状态"
+          label="错误选择"
+          [required]="true"
+          errorMessage="这是一个错误信息"
+          [(ngModel)]="selectedError">
+        </app-dropdown>
+      </div>
+
+      <div class="demo-section">
+        <h2>不同尺寸</h2>
+        <div class="size-demo">
+          <app-dropdown
+            [options]="basicOptions"
+            size="small"
+            placeholder="小尺寸"
+            label="小"
+            [(ngModel)]="selectedSmall">
+          </app-dropdown>
+          
+          <app-dropdown
+            [options]="basicOptions"
+            size="medium"
+            placeholder="中等尺寸"
+            label="中"
+            [(ngModel)]="selectedMedium">
+          </app-dropdown>
+          
+          <app-dropdown
+            [options]="basicOptions"
+            size="large"
+            placeholder="大尺寸"
+            label="大"
+            [(ngModel)]="selectedLarge">
+          </app-dropdown>
+        </div>
+      </div>
+    </div>
+  `,
+  styles: [`
+    .demo-container {
+      max-width: 800px;
+      margin: 0 auto;
+      padding: 20px;
+      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+    }
+
+    h1 {
+      color: #1D1D1F;
+      margin-bottom: 30px;
+      text-align: center;
+    }
+
+    h2 {
+      color: #1D1D1F;
+      margin-bottom: 15px;
+      font-size: 18px;
+    }
+
+    .demo-section {
+      margin-bottom: 40px;
+      padding: 20px;
+      background: #F2F2F7;
+      border-radius: 12px;
+    }
+
+    .demo-section p {
+      margin-top: 10px;
+      color: #8E8E93;
+      font-size: 14px;
+    }
+
+    .size-demo {
+      display: grid;
+      grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+      gap: 20px;
+    }
+  `]
+})
+export class DropdownDemoComponent {
+  // 基础选项
+  basicOptions: DropdownOption[] = [
+    { value: 'option1', label: '选项 1' },
+    { value: 'option2', label: '选项 2' },
+    { value: 'option3', label: '选项 3' },
+    { value: 'option4', label: '选项 4' },
+    { value: 'option5', label: '选项 5' }
+  ];
+
+  // 可搜索选项
+  searchableOptions: DropdownOption[] = [
+    { value: 'apple', label: '苹果' },
+    { value: 'banana', label: '香蕉' },
+    { value: 'orange', label: '橙子' },
+    { value: 'grape', label: '葡萄' },
+    { value: 'watermelon', label: '西瓜' },
+    { value: 'strawberry', label: '草莓' },
+    { value: 'pineapple', label: '菠萝' },
+    { value: 'mango', label: '芒果' }
+  ];
+
+  // 带图标选项
+  iconOptions: DropdownOption[] = [
+    { value: 'home', label: '首页', icon: 'home' },
+    { value: 'settings', label: '设置', icon: 'settings' },
+    { value: 'profile', label: '个人资料', icon: 'person' },
+    { value: 'notifications', label: '通知', icon: 'notifications' },
+    { value: 'help', label: '帮助', icon: 'help' }
+  ];
+
+  // 分组选项
+  groupedOptions: DropdownOption[] = [
+    { value: 'fruits', label: '水果', isGroup: true },
+    { value: 'apple', label: '苹果', group: 'fruits', icon: 'apple' },
+    { value: 'banana', label: '香蕉', group: 'fruits', icon: 'banana' },
+    { value: 'orange', label: '橙子', group: 'fruits', icon: 'orange' },
+    { value: 'vegetables', label: '蔬菜', isGroup: true },
+    { value: 'carrot', label: '胡萝卜', group: 'vegetables', icon: 'carrot' },
+    { value: 'broccoli', label: '西兰花', group: 'vegetables', icon: 'broccoli' },
+    { value: 'spinach', label: '菠菜', group: 'vegetables', icon: 'spinach' }
+  ];
+
+  accessibilityOptions: DropdownOption[] = [
+    { value: 'keyboard', label: '键盘导航', description: '支持方向键、Home、End键导航' },
+    { value: 'screen-reader', label: '屏幕阅读器', description: '完整的ARIA标签支持' },
+    { value: 'high-contrast', label: '高对比度', description: '支持高对比度模式' },
+    { value: 'reduced-motion', label: '减少动画', description: '尊重用户的动画偏好设置' },
+    { value: 'focus-management', label: '焦点管理', description: '合理的焦点顺序和可见性' },
+    { value: 'quick-search', label: '快速搜索', description: '按字母键快速定位选项' },
+    { value: 'disabled-option', label: '禁用选项', disabled: true, description: '此选项已禁用,无法选择' }
+  ];
+
+  // 选中值
+  selectedBasic: any = null;
+  selectedMultiple: any[] = [];
+  selectedSearchable: any = null;
+  selectedIcon: any = null;
+  selectedGrouped: any = null;
+  selectedAccessibility: any = null;
+  selectedDisabled: any = null;
+  selectedError: any = null;
+  selectedSmall: any = null;
+  selectedMedium: any = null;
+  selectedLarge: any = null;
+
+  // 事件处理
+  onBasicChange(value: any) {
+    console.log('基础选择变化:', value);
+  }
+
+  onMultipleChange(value: any[]) {
+    console.log('多选变化:', value);
+  }
+
+  onSearchableChange(value: any) {
+    console.log('搜索选择变化:', value);
+  }
+
+  onIconChange(value: any) {
+    console.log('图标选择变化:', value);
+  }
+
+  onGroupedChange(value: any) {
+    console.log('分组选择变化:', value);
+  }
+
+  onSelectionChange(type: string, value: any) {
+    console.log(`${type} selection changed:`, value);
+  }
+}

+ 347 - 0
src/app/pages/shared/dropdown/dropdown-example.component.ts

@@ -0,0 +1,347 @@
+import { Component } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
+import { DropdownComponent, DropdownOption } from './dropdown.component';
+
+@Component({
+  selector: 'app-dropdown-example',
+  standalone: true,
+  imports: [CommonModule, ReactiveFormsModule, DropdownComponent],
+  template: `
+    <div class="example-container">
+      <h2>下拉列表组件示例</h2>
+      
+      <form [formGroup]="exampleForm" class="example-form">
+        
+        <!-- 基础下拉列表 -->
+        <div class="form-section">
+          <h3>基础下拉列表</h3>
+          <app-dropdown
+            label="选择部门"
+            placeholder="请选择部门..."
+            [options]="departmentOptions"
+            formControlName="department"
+            [required]="true"
+            helpText="选择您所在的部门">
+          </app-dropdown>
+        </div>
+
+        <!-- 可搜索下拉列表 -->
+        <div class="form-section">
+          <h3>可搜索下拉列表</h3>
+          <app-dropdown
+            label="选择城市"
+            placeholder="搜索或选择城市..."
+            [options]="cityOptions"
+            formControlName="city"
+            [searchable]="true"
+            [clearable]="true">
+          </app-dropdown>
+        </div>
+
+        <!-- 多选下拉列表 -->
+        <div class="form-section">
+          <h3>多选下拉列表</h3>
+          <app-dropdown
+            label="选择技能"
+            placeholder="选择您的技能..."
+            [options]="skillOptions"
+            formControlName="skills"
+            [multiple]="true"
+            [searchable]="true"
+            helpText="可以选择多个技能">
+          </app-dropdown>
+        </div>
+
+        <!-- 带图标的下拉列表 -->
+        <div class="form-section">
+          <h3>带图标的下拉列表</h3>
+          <app-dropdown
+            label="选择状态"
+            placeholder="选择状态..."
+            [options]="statusOptions"
+            formControlName="status"
+            color="success">
+          </app-dropdown>
+        </div>
+
+        <!-- 分组下拉列表 -->
+        <div class="form-section">
+          <h3>分组下拉列表</h3>
+          <app-dropdown
+            label="选择职位"
+            placeholder="选择职位..."
+            [options]="positionOptions"
+            formControlName="position"
+            [searchable]="true">
+          </app-dropdown>
+        </div>
+
+        <!-- 不同尺寸 -->
+        <div class="form-section">
+          <h3>不同尺寸</h3>
+          <div class="size-examples">
+            <app-dropdown
+              label="小尺寸"
+              placeholder="小尺寸..."
+              [options]="sizeOptions"
+              formControlName="smallSize"
+              size="small">
+            </app-dropdown>
+            
+            <app-dropdown
+              label="中等尺寸"
+              placeholder="中等尺寸..."
+              [options]="sizeOptions"
+              formControlName="mediumSize"
+              size="medium">
+            </app-dropdown>
+            
+            <app-dropdown
+              label="大尺寸"
+              placeholder="大尺寸..."
+              [options]="sizeOptions"
+              formControlName="largeSize"
+              size="large">
+            </app-dropdown>
+          </div>
+        </div>
+
+        <!-- 不同样式变体 -->
+        <div class="form-section">
+          <h3>不同样式变体</h3>
+          <div class="variant-examples">
+            <app-dropdown
+              label="轮廓样式"
+              placeholder="轮廓样式..."
+              [options]="variantOptions"
+              formControlName="outlined"
+              variant="outlined">
+            </app-dropdown>
+            
+            <app-dropdown
+              label="填充样式"
+              placeholder="填充样式..."
+              [options]="variantOptions"
+              formControlName="filled"
+              variant="filled">
+            </app-dropdown>
+            
+            <app-dropdown
+              label="标准样式"
+              placeholder="标准样式..."
+              [options]="variantOptions"
+              formControlName="standard"
+              variant="standard">
+            </app-dropdown>
+          </div>
+        </div>
+
+        <!-- 错误状态 -->
+        <div class="form-section">
+          <h3>错误状态</h3>
+          <app-dropdown
+            label="必填字段"
+            placeholder="请选择..."
+            [options]="departmentOptions"
+            formControlName="requiredField"
+            [required]="true"
+            [errorMessage]="getErrorMessage('requiredField')">
+          </app-dropdown>
+        </div>
+
+        <!-- 禁用状态 -->
+        <div class="form-section">
+          <h3>禁用状态</h3>
+          <app-dropdown
+            label="禁用的下拉列表"
+            placeholder="已禁用..."
+            [options]="departmentOptions"
+            formControlName="disabled"
+            [disabled]="true">
+          </app-dropdown>
+        </div>
+
+      </form>
+
+      <!-- 表单值显示 -->
+      <div class="form-values">
+        <h3>表单值</h3>
+        <pre>{{ exampleForm.value | json }}</pre>
+      </div>
+    </div>
+  `,
+  styles: [`
+    .example-container {
+      max-width: 800px;
+      margin: 0 auto;
+      padding: 24px;
+      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+    }
+
+    h2 {
+      color: #1D1D1F;
+      margin-bottom: 32px;
+      text-align: center;
+    }
+
+    h3 {
+      color: #1D1D1F;
+      margin-bottom: 16px;
+      font-size: 18px;
+      font-weight: 600;
+    }
+
+    .example-form {
+      display: flex;
+      flex-direction: column;
+      gap: 32px;
+    }
+
+    .form-section {
+      display: flex;
+      flex-direction: column;
+      gap: 16px;
+    }
+
+    .size-examples,
+    .variant-examples {
+      display: grid;
+      grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+      gap: 16px;
+    }
+
+    .form-values {
+      margin-top: 32px;
+      padding: 16px;
+      background: #F8F9FA;
+      border-radius: 8px;
+      border: 1px solid #E9ECEF;
+    }
+
+    .form-values pre {
+      margin: 0;
+      font-size: 12px;
+      color: #495057;
+      white-space: pre-wrap;
+      word-break: break-word;
+    }
+
+    @media (max-width: 768px) {
+      .example-container {
+        padding: 16px;
+      }
+
+      .size-examples,
+      .variant-examples {
+        grid-template-columns: 1fr;
+      }
+    }
+  `]
+})
+export class DropdownExampleComponent {
+  exampleForm: FormGroup;
+
+  // 部门选项
+  departmentOptions: DropdownOption[] = [
+    { value: 'design', label: 'UI设计部', icon: 'design_services' },
+    { value: 'development', label: '前端开发部', icon: 'code' },
+    { value: 'backend', label: '后端开发部', icon: 'storage' },
+    { value: 'product', label: '产品部', icon: 'lightbulb' },
+    { value: 'marketing', label: '市场部', icon: 'campaign' },
+    { value: 'hr', label: '人事部', icon: 'people' }
+  ];
+
+  // 城市选项
+  cityOptions: DropdownOption[] = [
+    { value: 'beijing', label: '北京', icon: 'location_city' },
+    { value: 'shanghai', label: '上海', icon: 'location_city' },
+    { value: 'guangzhou', label: '广州', icon: 'location_city' },
+    { value: 'shenzhen', label: '深圳', icon: 'location_city' },
+    { value: 'hangzhou', label: '杭州', icon: 'location_city' },
+    { value: 'nanjing', label: '南京', icon: 'location_city' },
+    { value: 'wuhan', label: '武汉', icon: 'location_city' },
+    { value: 'chengdu', label: '成都', icon: 'location_city' },
+    { value: 'xian', label: '西安', icon: 'location_city' },
+    { value: 'qingdao', label: '青岛', icon: 'location_city' }
+  ];
+
+  // 技能选项
+  skillOptions: DropdownOption[] = [
+    { value: 'html', label: 'HTML', icon: 'code' },
+    { value: 'css', label: 'CSS', icon: 'palette' },
+    { value: 'javascript', label: 'JavaScript', icon: 'javascript' },
+    { value: 'typescript', label: 'TypeScript', icon: 'code' },
+    { value: 'angular', label: 'Angular', icon: 'web' },
+    { value: 'react', label: 'React', icon: 'web' },
+    { value: 'vue', label: 'Vue.js', icon: 'web' },
+    { value: 'nodejs', label: 'Node.js', icon: 'storage' },
+    { value: 'python', label: 'Python', icon: 'code' },
+    { value: 'java', label: 'Java', icon: 'code' }
+  ];
+
+  // 状态选项
+  statusOptions: DropdownOption[] = [
+    { value: 'active', label: '活跃', icon: 'check_circle' },
+    { value: 'inactive', label: '非活跃', icon: 'pause_circle' },
+    { value: 'pending', label: '待处理', icon: 'schedule' },
+    { value: 'blocked', label: '已阻止', icon: 'block' }
+  ];
+
+  // 职位选项(分组)
+  positionOptions: DropdownOption[] = [
+    { value: 'ui-junior', label: '初级UI设计师', group: '设计部门', icon: 'design_services' },
+    { value: 'ui-senior', label: '高级UI设计师', group: '设计部门', icon: 'design_services' },
+    { value: 'ux-designer', label: 'UX设计师', group: '设计部门', icon: 'psychology' },
+    { value: 'frontend-junior', label: '初级前端工程师', group: '开发部门', icon: 'code' },
+    { value: 'frontend-senior', label: '高级前端工程师', group: '开发部门', icon: 'code' },
+    { value: 'backend-junior', label: '初级后端工程师', group: '开发部门', icon: 'storage' },
+    { value: 'backend-senior', label: '高级后端工程师', group: '开发部门', icon: 'storage' },
+    { value: 'product-manager', label: '产品经理', group: '产品部门', icon: 'lightbulb' },
+    { value: 'product-owner', label: '产品负责人', group: '产品部门', icon: 'lightbulb' }
+  ];
+
+  // 尺寸选项
+  sizeOptions: DropdownOption[] = [
+    { value: 'xs', label: '超小' },
+    { value: 's', label: '小' },
+    { value: 'm', label: '中' },
+    { value: 'l', label: '大' },
+    { value: 'xl', label: '超大' }
+  ];
+
+  // 样式变体选项
+  variantOptions: DropdownOption[] = [
+    { value: 'option1', label: '选项一' },
+    { value: 'option2', label: '选项二' },
+    { value: 'option3', label: '选项三' }
+  ];
+
+  constructor(private fb: FormBuilder) {
+    this.exampleForm = this.fb.group({
+      department: ['', Validators.required],
+      city: [''],
+      skills: [[]],
+      status: [''],
+      position: [''],
+      smallSize: [''],
+      mediumSize: [''],
+      largeSize: [''],
+      outlined: [''],
+      filled: [''],
+      standard: [''],
+      requiredField: ['', Validators.required],
+      disabled: [{ value: 'design', disabled: true }]
+    });
+  }
+
+  getErrorMessage(fieldName: string): string {
+    const control = this.exampleForm.get(fieldName);
+    if (control?.errors && control.touched) {
+      if (control.errors['required']) {
+        return '此字段为必填项';
+      }
+    }
+    return '';
+  }
+}

+ 226 - 0
src/app/pages/shared/dropdown/dropdown.component.html

@@ -0,0 +1,226 @@
+<div class="dropdown-container" 
+     [class.disabled]="disabled" 
+     [class.error]="hasError"
+     [class.focused]="isOpen"
+     [class]="'dropdown-' + size"
+     [class]="'dropdown-' + variant"
+     [class]="'dropdown-' + color">
+  
+  <!-- 标签 -->
+  @if (label) {
+    <label class="dropdown-label" [class.required]="required">
+      {{ label }}
+      @if (required) {
+        <span class="required-asterisk">*</span>
+      }
+    </label>
+  }
+
+  <!-- 主要输入区域 -->
+  <div class="dropdown-input-wrapper" 
+       (click)="toggleDropdown()"
+       [attr.tabindex]="disabled ? -1 : 0"
+       (keydown)="onKeyDown($event)"
+       [attr.aria-expanded]="isOpen"
+       [attr.aria-haspopup]="'listbox'"
+       [attr.aria-label]="label || placeholder"
+       [attr.aria-describedby]="helpText ? 'dropdown-help-' + label : null"
+       [attr.aria-invalid]="hasError"
+       role="combobox">
+    
+    <!-- 选中值显示 -->
+    <div class="dropdown-display">
+      @if (multiple && selectedOptions.length > 0) {
+        <!-- 多选标签 -->
+        <div class="selected-tags">
+          @for (option of selectedOptions; track option.value) {
+            <span class="selected-tag">
+              @if (option.icon) {
+                <mat-icon class="tag-icon">{{ option.icon }}</mat-icon>
+              }
+              {{ option.label }}
+              <button type="button" 
+                      class="tag-remove" 
+                      (click)="removeOption($event, option)"
+                      [attr.aria-label]="'移除 ' + option.label">
+                <mat-icon>close</mat-icon>
+              </button>
+            </span>
+          }
+        </div>
+      } @else if (!multiple && selectedOption) {
+        <!-- 单选显示 -->
+        <div class="selected-single">
+          @if (selectedOption.icon) {
+            <mat-icon class="selected-icon">{{ selectedOption.icon }}</mat-icon>
+          }
+          <span class="selected-text">{{ selectedOption.label }}</span>
+        </div>
+      } @else {
+        <!-- 占位符 -->
+        <span class="dropdown-placeholder">{{ placeholder }}</span>
+      }
+    </div>
+
+    <!-- 右侧图标 -->
+    <div class="dropdown-icons">
+      @if (clearable && hasValue && !disabled) {
+        <button type="button" 
+                class="clear-button" 
+                (click)="clearSelection($event)"
+                [attr.aria-label]="'清除选择'">
+          <mat-icon>close</mat-icon>
+        </button>
+      }
+      <mat-icon class="dropdown-arrow" [class.rotated]="isOpen">
+        keyboard_arrow_down
+      </mat-icon>
+    </div>
+  </div>
+
+  <!-- 下拉面板 -->
+  @if (isOpen) {
+    <div class="dropdown-panel" 
+         [@slideInOut]
+         (click)="$event.stopPropagation()"
+         role="listbox"
+         [attr.aria-label]="label + '选项列表'"
+         [attr.aria-multiselectable]="multiple">
+      
+      <!-- 搜索框 -->
+      @if (searchable) {
+        <div class="dropdown-search">
+          <mat-icon class="search-icon">search</mat-icon>
+          <input type="text"
+                 class="search-input"
+                 [(ngModel)]="searchTerm"
+                 (input)="onSearch()"
+                 [placeholder]="'搜索...'"
+                 [attr.aria-label]="'搜索' + label + '选项'"
+                 [attr.aria-describedby]="'search-help-' + label"
+                 role="searchbox"
+                 #searchInput>
+          @if (searchTerm) {
+            <button type="button" 
+                    class="search-clear" 
+                    (click)="clearSearch()">
+              <mat-icon>close</mat-icon>
+            </button>
+          }
+        </div>
+      }
+
+      <!-- 选项列表 -->
+      <div class="dropdown-options" [class.with-search]="searchable">
+        @if (filteredOptions.length === 0) {
+          <div class="no-options">
+            @if (searchTerm) {
+              <mat-icon>search_off</mat-icon>
+              <span>未找到匹配项</span>
+            } @else {
+              <mat-icon>inbox</mat-icon>
+              <span>暂无选项</span>
+            }
+          </div>
+        } @else {
+          @if (groupedOptions.size > 0) {
+            <!-- 分组选项 -->
+            @for (group of groupedOptions | keyvalue; track group.key) {
+              <div class="option-group">
+                <div class="group-header">{{ group.key }}</div>
+                @for (option of group.value; track option.value) {
+                  <div class="dropdown-option"
+                       [class.selected]="isSelected(option)"
+                       [class.highlighted]="highlightedIndex === getOptionIndex(option)"
+                       (click)="selectOption(option)"
+                       (mouseenter)="highlightedIndex = getOptionIndex(option)"
+                       [attr.aria-disabled]="option.disabled || option.isGroup">
+                    
+                    @if (multiple) {
+                      <div class="option-checkbox">
+                        <mat-icon>{{ isSelected(option) ? 'check_box' : 'check_box_outline_blank' }}</mat-icon>
+                      </div>
+                    }
+                    
+                    @if (option.icon) {
+                      <mat-icon class="option-icon">{{ option.icon }}</mat-icon>
+                    }
+                    
+                    <span class="option-text">{{ option.label }}</span>
+                    
+                    @if (option.description) {
+                      <span class="option-description">{{ option.description }}</span>
+                    }
+                  </div>
+                }
+              </div>
+            }
+          } @else {
+            <!-- 普通选项 -->
+            @for (option of filteredOptions; track option.value; let i = $index) {
+              <div class="dropdown-option"
+                   [class.selected]="isSelected(option)"
+                   [class.disabled]="option.disabled"
+                   [class.highlighted]="highlightedIndex === i"
+                   (click)="selectOption(option)"
+                   (mouseenter)="highlightedIndex = i"
+                   role="option"
+                   [attr.aria-selected]="isSelected(option)"
+                   [attr.aria-disabled]="option.disabled || option.isGroup"
+                   [attr.aria-label]="option.label"
+                   [attr.id]="'option-' + i">
+                
+                @if (multiple) {
+                  <div class="option-checkbox">
+                    <mat-icon>{{ isSelected(option) ? 'check_box' : 'check_box_outline_blank' }}</mat-icon>
+                  </div>
+                }
+                
+                @if (option.icon) {
+                  <mat-icon class="option-icon">{{ option.icon }}</mat-icon>
+                }
+                
+                <span class="option-text">{{ option.label }}</span>
+                
+                @if (option.description) {
+                  <span class="option-description">{{ option.description }}</span>
+                }
+              </div>
+            }
+          }
+        }
+      </div>
+    </div>
+  }
+
+  <!-- 帮助文本或错误信息 -->
+  @if (helpText || errorMessage) {
+    <div class="dropdown-hint">
+      @if (errorMessage && hasError) {
+        <mat-icon class="hint-icon error">error</mat-icon>
+        <span class="hint-text error">{{ errorMessage }}</span>
+      } @else if (helpText) {
+        <mat-icon class="hint-icon">info</mat-icon>
+        <span class="hint-text">{{ helpText }}</span>
+      }
+    </div>
+  }
+</div>
+
+<!-- 背景遮罩 -->
+@if (isOpen) {
+  <div class="dropdown-backdrop" (click)="closeDropdown()"></div>
+}
+
+<!-- 无障碍帮助文本 -->
+@if (helpText) {
+  <div [id]="'dropdown-help-' + label" class="sr-only">
+    {{ helpText }}
+  </div>
+}
+
+@if (searchable) {
+  <div [id]="'search-help-' + label" class="sr-only">
+    使用键盘上下箭头键导航选项,回车键选择,Escape键关闭下拉列表
+  </div>
+}

+ 804 - 0
src/app/pages/shared/dropdown/dropdown.component.scss

@@ -0,0 +1,804 @@
+// 下拉列表组件样式
+.dropdown-container {
+  position: relative;
+  width: 100%;
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+
+  // 禁用状态
+  &.disabled {
+    opacity: 0.6;
+    pointer-events: none;
+  }
+
+  // 错误状态
+  &.error {
+    .dropdown-wrapper {
+      border-color: #FF3B30;
+      box-shadow: 0 0 0 2px rgba(255, 59, 48, 0.1);
+    }
+  }
+}
+
+// 背景遮罩
+.dropdown-backdrop {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 999;
+  background: rgba(0, 0, 0, 0.1);
+  backdrop-filter: blur(2px);
+  opacity: 0;
+  animation: fadeIn 0.2s ease forwards;
+}
+
+@keyframes fadeIn {
+  to {
+    opacity: 1;
+  }
+}
+
+// 响应式设计
+@media (max-width: 768px) {
+  .dropdown-container {
+    .dropdown-panel {
+      left: -8px;
+      right: -8px;
+      max-height: 60vh;
+    }
+
+    .dropdown-search {
+      padding: 16px;
+    }
+
+    .dropdown-option {
+      padding: 16px;
+      min-height: 56px;
+    }
+  }
+}
+
+// 高对比度模式支持
+@media (prefers-contrast: high) {
+  .dropdown-container {
+    .dropdown-wrapper {
+      border-width: 3px;
+    }
+
+    .dropdown-panel {
+      border-width: 2px;
+      box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
+    }
+
+    .dropdown-option {
+      &:hover:not(.disabled):not(.group-header) {
+        background: rgba(0, 122, 255, 0.2);
+      }
+
+      &.selected:not(.group-header) {
+        background: rgba(0, 122, 255, 0.3);
+      }
+    }
+  }
+}
+
+// 减少动画模式支持
+@media (prefers-reduced-motion: reduce) {
+  .dropdown-container {
+    * {
+      transition: none !important;
+      animation: none !important;
+    }
+  }
+}
+
+// 屏幕阅读器专用类
+.sr-only {
+  position: absolute;
+  width: 1px;
+  height: 1px;
+  padding: 0;
+  margin: -1px;
+  overflow: hidden;
+  clip: rect(0, 0, 0, 0);
+  white-space: nowrap;
+  border: 0;
+}
+
+// 标签样式
+.dropdown-label {
+  display: block;
+  font-size: 14px;
+  font-weight: 600;
+  color: #1D1D1F;
+  margin-bottom: 8px;
+  line-height: 1.4;
+
+  &.required {
+    .required-asterisk {
+      color: #FF3B30;
+      margin-left: 4px;
+    }
+  }
+}
+
+// 下拉框包装器
+.dropdown-wrapper {
+  position: relative;
+  background: #FFFFFF;
+  border: 2px solid #E5E5E7;
+  border-radius: 12px;
+  transition: all 0.2s cubic-bezier(0.25, 0.8, 0.25, 1);
+  cursor: pointer;
+  min-height: 48px;
+  
+  // 悬停状态
+  &:hover:not(.disabled) {
+    border-color: #007AFF;
+    box-shadow: 0 2px 8px rgba(0, 122, 255, 0.1);
+  }
+
+  // 聚焦状态
+  &.focused {
+    border-color: #007AFF;
+    box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.15);
+  }
+
+  // 打开状态
+  &.open {
+    border-color: #007AFF;
+    box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.15);
+    border-bottom-left-radius: 4px;
+    border-bottom-right-radius: 4px;
+  }
+
+  // 有值状态
+  &.has-value {
+    .selected-text {
+      color: #1D1D1F;
+    }
+  }
+}
+
+// 选中值显示区域
+.dropdown-display {
+  display: flex;
+  align-items: center;
+  padding: 12px 16px;
+  min-height: 24px;
+  gap: 8px;
+
+  .selected-icon {
+    font-size: 20px;
+    color: #8E8E93;
+    flex-shrink: 0;
+  }
+
+  .selected-text {
+    flex: 1;
+    font-size: 16px;
+    line-height: 1.5;
+    color: #1D1D1F;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+
+    &.placeholder {
+      color: #8E8E93;
+    }
+  }
+
+  .clear-button {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 24px;
+    height: 24px;
+    border: none;
+    background: #F2F2F7;
+    border-radius: 50%;
+    cursor: pointer;
+    transition: all 0.2s ease;
+    flex-shrink: 0;
+
+    &:hover {
+      background: #E5E5EA;
+      transform: scale(1.1);
+    }
+
+    mat-icon {
+      font-size: 16px;
+      color: #8E8E93;
+    }
+  }
+
+  .dropdown-arrow {
+    font-size: 24px;
+    color: #8E8E93;
+    transition: transform 0.2s cubic-bezier(0.25, 0.8, 0.25, 1);
+    flex-shrink: 0;
+
+    &.rotated {
+      transform: rotate(180deg);
+    }
+  }
+}
+
+// 下拉面板
+.dropdown-panel {
+  position: absolute;
+  top: calc(100% + 4px);
+  left: 0;
+  right: 0;
+  z-index: 1000;
+  background: rgba(255, 255, 255, 0.95);
+  border: 1px solid rgba(0, 122, 255, 0.2);
+  border-radius: 12px;
+  box-shadow: 
+    0 8px 32px rgba(0, 0, 0, 0.12),
+    0 2px 8px rgba(0, 0, 0, 0.08),
+    inset 0 1px 0 rgba(255, 255, 255, 0.8);
+  backdrop-filter: blur(20px);
+  overflow: hidden;
+  max-height: 320px;
+  
+  // 确保在深色背景下也能清晰显示
+  @media (prefers-color-scheme: dark) {
+    background: rgba(28, 28, 30, 0.95);
+    border-color: rgba(255, 255, 255, 0.1);
+    box-shadow: 
+      0 8px 32px rgba(0, 0, 0, 0.3),
+      0 2px 8px rgba(0, 0, 0, 0.2),
+      inset 0 1px 0 rgba(255, 255, 255, 0.1);
+  }
+}
+
+// 搜索容器
+.dropdown-search {
+  position: relative;
+  padding: 12px;
+  border-bottom: 1px solid rgba(242, 242, 247, 0.6);
+  background: rgba(251, 251, 253, 0.8);
+
+  .search-icon {
+    position: absolute;
+    left: 20px;
+    top: 50%;
+    transform: translateY(-50%);
+    font-size: 20px;
+    color: #8E8E93;
+    pointer-events: none;
+  }
+
+  .search-input {
+    width: 100%;
+    padding: 10px 16px 10px 44px;
+    border: 1px solid rgba(229, 229, 231, 0.8);
+    border-radius: 8px;
+    font-size: 14px;
+    background: rgba(255, 255, 255, 0.9);
+    outline: none;
+    transition: all 0.2s ease;
+
+    &:focus {
+      border-color: #007AFF;
+      background: #FFFFFF;
+      box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.1);
+    }
+
+    &::placeholder {
+      color: #8E8E93;
+    }
+  }
+
+  .search-clear {
+    position: absolute;
+    right: 20px;
+    top: 50%;
+    transform: translateY(-50%);
+    width: 20px;
+    height: 20px;
+    border: none;
+    background: rgba(142, 142, 147, 0.2);
+    border-radius: 50%;
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    transition: all 0.2s ease;
+
+    &:hover {
+      background: rgba(142, 142, 147, 0.3);
+      transform: translateY(-50%) scale(1.1);
+    }
+
+    mat-icon {
+      font-size: 14px;
+      color: #8E8E93;
+    }
+  }
+
+  // 深色模式适配
+  @media (prefers-color-scheme: dark) {
+    background: rgba(44, 44, 46, 0.8);
+    border-bottom-color: rgba(255, 255, 255, 0.1);
+
+    .search-input {
+      background: rgba(58, 58, 60, 0.9);
+      border-color: rgba(255, 255, 255, 0.1);
+      color: #FFFFFF;
+
+      &:focus {
+        background: rgba(58, 58, 60, 1);
+        border-color: #007AFF;
+      }
+
+      &::placeholder {
+        color: rgba(235, 235, 245, 0.6);
+      }
+    }
+  }
+}
+
+// 选项列表
+.options-list {
+  overflow-y: auto;
+  
+  // 自定义滚动条
+  &::-webkit-scrollbar {
+    width: 6px;
+  }
+
+  &::-webkit-scrollbar-track {
+    background: transparent;
+  }
+
+  &::-webkit-scrollbar-thumb {
+    background: #C7C7CC;
+    border-radius: 3px;
+    
+    &:hover {
+      background: #AEAEB2;
+    }
+  }
+}
+
+// 无选项提示
+.no-options {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 32px 16px;
+  color: #8E8E93;
+  text-align: center;
+
+  mat-icon {
+    font-size: 32px;
+    margin-bottom: 8px;
+    opacity: 0.6;
+  }
+
+  span {
+    font-size: 14px;
+  }
+}
+
+// 下拉选项
+.dropdown-option {
+  display: flex;
+  align-items: center;
+  padding: 12px 16px;
+  cursor: pointer;
+  transition: all 0.15s ease;
+  border-bottom: 1px solid rgba(242, 242, 247, 0.5);
+  gap: 12px;
+  min-height: 48px;
+
+  &:last-child {
+    border-bottom: none;
+  }
+
+  // 悬停状态
+  &:hover:not(.disabled):not(.group-header) {
+    background: linear-gradient(135deg, rgba(0, 122, 255, 0.08) 0%, rgba(0, 122, 255, 0.04) 100%);
+    transform: translateX(2px);
+  }
+
+  // 选中状态
+  &.selected:not(.group-header) {
+    background: linear-gradient(135deg, rgba(0, 122, 255, 0.12) 0%, rgba(0, 122, 255, 0.06) 100%);
+    color: #007AFF;
+    font-weight: 600;
+
+    .option-icon {
+      color: #007AFF;
+    }
+  }
+
+  // 禁用状态
+  &.disabled {
+    opacity: 0.5;
+    cursor: not-allowed;
+  }
+
+  // 分组标题
+  &.group-header {
+    background: #F8F9FA;
+    cursor: default;
+    font-weight: 600;
+    color: #6C757D;
+    font-size: 12px;
+    text-transform: uppercase;
+    letter-spacing: 0.5px;
+    padding: 8px 16px;
+    border-bottom: 1px solid #E9ECEF;
+
+    .group-header {
+      width: 100%;
+    }
+  }
+
+  .option-icon {
+    font-size: 20px;
+    color: #8E8E93;
+    flex-shrink: 0;
+    transition: color 0.15s ease;
+  }
+
+  .option-label {
+    flex: 1;
+    font-size: 15px;
+    line-height: 1.4;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+  }
+
+  .check-icon {
+    font-size: 20px;
+    color: #34C759;
+    flex-shrink: 0;
+  }
+}
+
+// 错误信息
+.error-message {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  margin-top: 6px;
+  color: #FF3B30;
+  font-size: 13px;
+  line-height: 1.4;
+
+  mat-icon {
+    font-size: 16px;
+  }
+}
+
+// 帮助文本
+.help-text {
+  margin-top: 6px;
+  color: #8E8E93;
+  font-size: 13px;
+  line-height: 1.4;
+}
+
+// 尺寸变体
+.dropdown-container {
+  // 小尺寸
+  &.size-small {
+    .dropdown-wrapper {
+      min-height: 36px;
+    }
+
+    .dropdown-display {
+      padding: 8px 12px;
+      
+      .selected-text {
+        font-size: 14px;
+      }
+    }
+
+    .dropdown-option {
+      padding: 8px 12px;
+      min-height: 36px;
+      
+      .option-label {
+        font-size: 14px;
+      }
+    }
+  }
+
+  // 大尺寸
+  &.size-large {
+    .dropdown-wrapper {
+      min-height: 56px;
+    }
+
+    .dropdown-display {
+      padding: 16px 20px;
+      
+      .selected-text {
+        font-size: 18px;
+      }
+    }
+
+    .dropdown-option {
+      padding: 16px 20px;
+      min-height: 56px;
+      
+      .option-label {
+        font-size: 16px;
+      }
+    }
+  }
+}
+
+// 样式变体
+.dropdown-container {
+  // 填充样式
+  &.variant-filled {
+    .dropdown-wrapper {
+      background: #F2F2F7;
+      border: 2px solid transparent;
+
+      &:hover:not(.disabled) {
+        background: #E5E5EA;
+        border-color: #007AFF;
+      }
+
+      &.focused,
+      &.open {
+        background: #FFFFFF;
+        border-color: #007AFF;
+      }
+    }
+  }
+
+  // 标准样式
+  &.variant-standard {
+    .dropdown-wrapper {
+      background: transparent;
+      border: none;
+      border-bottom: 2px solid #E5E5E7;
+      border-radius: 0;
+
+      &:hover:not(.disabled) {
+        border-bottom-color: #007AFF;
+      }
+
+      &.focused,
+      &.open {
+        border-bottom-color: #007AFF;
+      }
+    }
+
+    .dropdown-options {
+      border: 1px solid #E5E5E7;
+      border-radius: 8px;
+      margin-top: 4px;
+    }
+  }
+}
+
+// 颜色变体
+.dropdown-container {
+  &.color-secondary {
+    &.focused,
+    &.open {
+      .dropdown-wrapper {
+        border-color: #5856D6;
+        box-shadow: 0 0 0 3px rgba(88, 86, 214, 0.15);
+      }
+    }
+
+    .dropdown-option.selected {
+      background: linear-gradient(135deg, rgba(88, 86, 214, 0.12) 0%, rgba(88, 86, 214, 0.06) 100%);
+      color: #5856D6;
+    }
+  }
+
+  &.color-success {
+    &.focused,
+    &.open {
+      .dropdown-wrapper {
+        border-color: #34C759;
+        box-shadow: 0 0 0 3px rgba(52, 199, 89, 0.15);
+      }
+    }
+
+    .dropdown-option.selected {
+      background: linear-gradient(135deg, rgba(52, 199, 89, 0.12) 0%, rgba(52, 199, 89, 0.06) 100%);
+      color: #34C759;
+    }
+  }
+
+  &.color-warning {
+    &.focused,
+    &.open {
+      .dropdown-wrapper {
+        border-color: #FF9500;
+        box-shadow: 0 0 0 3px rgba(255, 149, 0, 0.15);
+      }
+    }
+
+    .dropdown-option.selected {
+      background: linear-gradient(135deg, rgba(255, 149, 0, 0.12) 0%, rgba(255, 149, 0, 0.06) 100%);
+      color: #FF9500;
+    }
+  }
+
+  &.color-error {
+    &.focused,
+    &.open {
+      .dropdown-wrapper {
+        border-color: #FF3B30;
+        box-shadow: 0 0 0 3px rgba(255, 59, 48, 0.15);
+      }
+    }
+
+    .dropdown-option.selected {
+      background: linear-gradient(135deg, rgba(255, 59, 48, 0.12) 0%, rgba(255, 59, 48, 0.06) 100%);
+      color: #FF3B30;
+    }
+  }
+}
+
+// 响应式设计
+@media (max-width: 768px) {
+  .dropdown-container {
+    .dropdown-wrapper {
+      min-height: 44px;
+    }
+
+    .dropdown-display {
+      padding: 10px 14px;
+      
+      .selected-text {
+        font-size: 16px;
+      }
+    }
+
+    .dropdown-option {
+      padding: 14px 16px;
+      min-height: 44px;
+      
+      .option-label {
+        font-size: 16px;
+      }
+    }
+
+    .search-container {
+      padding: 16px;
+      
+      .search-input {
+        padding: 12px 16px 12px 44px;
+        font-size: 16px; // 防止iOS缩放
+      }
+    }
+  }
+}
+
+// 深色模式支持
+@media (prefers-color-scheme: dark) {
+  .dropdown-container {
+    .dropdown-label {
+      color: #F2F2F7;
+    }
+
+    .dropdown-wrapper {
+      background: #1C1C1E;
+      border-color: #38383A;
+
+      &:hover:not(.disabled) {
+        border-color: #0A84FF;
+      }
+
+      &.focused,
+      &.open {
+        border-color: #0A84FF;
+        box-shadow: 0 0 0 3px rgba(10, 132, 255, 0.15);
+      }
+    }
+
+    .dropdown-display {
+      .selected-text {
+        color: #F2F2F7;
+
+        &.placeholder {
+          color: #8E8E93;
+        }
+      }
+
+      .clear-button {
+        background: #2C2C2E;
+
+        &:hover {
+          background: #3A3A3C;
+        }
+      }
+    }
+
+    .dropdown-options {
+      background: #1C1C1E;
+      border-color: #0A84FF;
+      box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
+    }
+
+    .search-container {
+      background: #2C2C2E;
+      border-bottom-color: #38383A;
+
+      .search-input {
+        background: #1C1C1E;
+        border-color: #38383A;
+        color: #F2F2F7;
+
+        &:focus {
+          border-color: #0A84FF;
+        }
+      }
+    }
+
+    .dropdown-option {
+      border-bottom-color: rgba(56, 56, 58, 0.5);
+      color: #F2F2F7;
+
+      &:hover:not(.disabled):not(.group-header) {
+        background: linear-gradient(135deg, rgba(10, 132, 255, 0.12) 0%, rgba(10, 132, 255, 0.06) 100%);
+      }
+
+      &.selected:not(.group-header) {
+        background: linear-gradient(135deg, rgba(10, 132, 255, 0.15) 0%, rgba(10, 132, 255, 0.08) 100%);
+        color: #0A84FF;
+      }
+
+      &.group-header {
+        background: #2C2C2E;
+        color: #8E8E93;
+        border-bottom-color: #38383A;
+      }
+    }
+
+    .no-options {
+      color: #8E8E93;
+    }
+
+    .error-message {
+      color: #FF453A;
+    }
+
+    .help-text {
+      color: #8E8E93;
+    }
+  }
+}
+
+// 高对比度模式
+@media (prefers-contrast: high) {
+  .dropdown-container {
+    .dropdown-wrapper {
+      border-width: 3px;
+    }
+
+    .dropdown-option {
+      &.selected:not(.group-header) {
+        background: #007AFF;
+        color: #FFFFFF;
+      }
+    }
+  }
+}
+
+// 减少动画模式
+@media (prefers-reduced-motion: reduce) {
+  .dropdown-container {
+    * {
+      transition: none !important;
+      animation: none !important;
+    }
+  }
+}

+ 490 - 0
src/app/pages/shared/dropdown/dropdown.component.ts

@@ -0,0 +1,490 @@
+import { Component, Input, Output, EventEmitter, forwardRef, OnInit, OnDestroy, ElementRef, ViewChild } from '@angular/core';
+import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms';
+import { CommonModule } from '@angular/common';
+import { MatIconModule } from '@angular/material/icon';
+import { MatButtonModule } from '@angular/material/button';
+import { trigger, state, style, transition, animate } from '@angular/animations';
+
+export interface DropdownOption {
+  value: any;
+  label: string;
+  icon?: string;
+  disabled?: boolean;
+  group?: string;
+  description?: string;
+  isGroup?: boolean;
+}
+
+@Component({
+  selector: 'app-dropdown',
+  standalone: true,
+  imports: [CommonModule, MatIconModule, MatButtonModule, FormsModule],
+  templateUrl: './dropdown.component.html',
+  styleUrls: ['./dropdown.component.scss'],
+  providers: [
+    {
+      provide: NG_VALUE_ACCESSOR,
+      useExisting: forwardRef(() => DropdownComponent),
+      multi: true
+    }
+  ],
+  animations: [
+    trigger('slideInOut', [
+      transition(':enter', [
+        style({
+          opacity: 0,
+          transform: 'translateY(-10px) scale(0.95)'
+        }),
+        animate('200ms cubic-bezier(0.25, 0.8, 0.25, 1)', style({
+          opacity: 1,
+          transform: 'translateY(0) scale(1)'
+        }))
+      ]),
+      transition(':leave', [
+        animate('150ms cubic-bezier(0.25, 0.8, 0.25, 1)', style({
+          opacity: 0,
+          transform: 'translateY(-10px) scale(0.95)'
+        }))
+      ])
+    ])
+  ]
+})
+export class DropdownComponent implements ControlValueAccessor, OnInit, OnDestroy {
+  @ViewChild('dropdownWrapper') dropdownWrapper!: ElementRef;
+  @ViewChild('optionsContainer') optionsContainer!: ElementRef;
+  @ViewChild('searchInput') searchInput!: ElementRef;
+
+  // 基础配置
+  @Input() options: DropdownOption[] = [];
+  @Input() placeholder: string = '请选择...';
+  @Input() label: string = '';
+  @Input() disabled: boolean = false;
+  @Input() required: boolean = false;
+  @Input() multiple: boolean = false;
+  @Input() clearable: boolean = true;
+  @Input() searchable: boolean = false;
+  @Input() maxHeight: number = 300;
+  
+  // 样式配置
+  @Input() size: 'small' | 'medium' | 'large' = 'medium';
+  @Input() variant: 'outlined' | 'filled' | 'standard' = 'outlined';
+  @Input() color: 'primary' | 'secondary' | 'success' | 'warning' | 'error' = 'primary';
+  
+  // 验证
+  @Input() errorMessage: string = '';
+  @Input() helpText: string = '';
+  
+  // 事件
+  @Output() selectionChange = new EventEmitter<any>();
+  @Output() opened = new EventEmitter<void>();
+  @Output() closed = new EventEmitter<void>();
+  @Output() searchChange = new EventEmitter<string>();
+
+  // 组件状态
+  isOpen: boolean = false;
+  isFocused: boolean = false;
+  searchTerm: string = '';
+  selectedValue: any = null;
+  selectedValues: any[] = [];
+  filteredOptions: DropdownOption[] = [];
+  highlightedIndex: number = -1;
+  groupedOptions: Map<string, DropdownOption[]> = new Map();
+
+  // ControlValueAccessor
+  private onChange = (value: any) => {};
+  private onTouched = () => {};
+
+  constructor(private elementRef: ElementRef) {}
+
+  ngOnInit() {
+    this.filteredOptions = [...this.options];
+    this.updateGroupedOptions();
+  }
+
+  ngOnDestroy() {
+    this.removeClickOutside();
+  }
+
+  // ControlValueAccessor 实现
+  writeValue(value: any): void {
+    if (this.multiple) {
+      this.selectedValues = Array.isArray(value) ? value : [];
+    } else {
+      this.selectedValue = value;
+    }
+  }
+
+  registerOnChange(fn: any): void {
+    this.onChange = fn;
+  }
+
+  registerOnTouched(fn: any): void {
+    this.onTouched = fn;
+  }
+
+  setDisabledState(isDisabled: boolean): void {
+    this.disabled = isDisabled;
+  }
+
+  // 计算属性
+  get hasValue(): boolean {
+    return this.multiple ? this.selectedValues.length > 0 : this.selectedValue !== null && this.selectedValue !== undefined;
+  }
+
+  get hasError(): boolean {
+    return !!this.errorMessage;
+  }
+
+  get selectedOption(): DropdownOption | null {
+    if (this.multiple) return null;
+    return this.options.find(option => option.value === this.selectedValue) || null;
+  }
+
+  get displayText(): string {
+    if (this.multiple) {
+      if (this.selectedValues.length === 0) {
+        return this.placeholder;
+      } else {
+        return `已选择 ${this.selectedValues.length} 项`;
+      }
+    } else {
+      return this.selectedOption?.label || this.placeholder;
+    }
+  }
+
+  get selectedOptions(): DropdownOption[] {
+    if (!this.multiple) return [];
+    return this.options.filter(option => this.selectedValues.includes(option.value));
+  }
+
+  // 交互方法(保留旧方法以兼容)
+  toggle(): void {
+    this.toggleDropdown();
+  }
+
+  open(): void {
+    this.openDropdown();
+  }
+
+  close(): void {
+    this.closeDropdown();
+  }
+
+  selectOption(option: DropdownOption, event?: Event): void {
+    if (event) {
+      event.stopPropagation();
+    }
+
+    if (option.disabled) return;
+
+    if (this.multiple) {
+      const index = this.selectedValues.indexOf(option.value);
+      if (index > -1) {
+        this.selectedValues.splice(index, 1);
+      } else {
+        this.selectedValues.push(option.value);
+      }
+      this.onChange([...this.selectedValues]);
+      this.selectionChange.emit([...this.selectedValues]);
+    } else {
+      this.selectedValue = option.value;
+      this.onChange(this.selectedValue);
+      this.selectionChange.emit(this.selectedValue);
+      this.closeDropdown();
+    }
+  }
+
+  clear(event: Event): void {
+    this.clearSelection(event);
+  }
+
+  isSelected(option: DropdownOption): boolean {
+    if (this.multiple) {
+      return this.selectedValues.includes(option.value);
+    } else {
+      return this.selectedValue === option.value;
+    }
+  }
+
+  isGroupHeader(option: DropdownOption): boolean {
+    if (!option.group) return false;
+    const index = this.filteredOptions.indexOf(option);
+    if (index === 0) return true;
+    const prevOption = this.filteredOptions[index - 1];
+    return prevOption.group !== option.group;
+  }
+
+  onSearch(): void {
+    this.filteredOptions = this.options.filter(option => 
+      option.label.toLowerCase().includes(this.searchTerm.toLowerCase())
+    );
+    this.updateGroupedOptions();
+    this.highlightedIndex = -1;
+    this.searchChange.emit(this.searchTerm);
+  }
+
+  clearSearch(): void {
+    this.searchTerm = '';
+    this.onSearch();
+  }
+
+  toggleDropdown(): void {
+    if (this.disabled) return;
+    
+    if (this.isOpen) {
+      this.closeDropdown();
+    } else {
+      this.openDropdown();
+    }
+  }
+
+  openDropdown(): void {
+    if (this.disabled) return;
+    
+    this.isOpen = true;
+    this.isFocused = true;
+    this.filteredOptions = [...this.options];
+    this.updateGroupedOptions();
+    this.highlightedIndex = -1;
+    this.setupClickOutside();
+    this.opened.emit();
+
+    // 聚焦搜索框
+    if (this.searchable) {
+      setTimeout(() => {
+        const searchInput = document.querySelector('.search-input') as HTMLInputElement;
+        if (searchInput) {
+          searchInput.focus();
+        }
+      }, 100);
+    }
+  }
+
+  closeDropdown(): void {
+    this.isOpen = false;
+    this.isFocused = false;
+    this.searchTerm = '';
+    this.highlightedIndex = -1;
+    this.removeClickOutside();
+    this.closed.emit();
+    this.onTouched();
+  }
+
+  removeOption(event: Event, option: DropdownOption): void {
+    event.stopPropagation();
+    
+    if (this.multiple) {
+      const index = this.selectedValues.indexOf(option.value);
+      if (index > -1) {
+        this.selectedValues.splice(index, 1);
+        this.onChange([...this.selectedValues]);
+        this.selectionChange.emit([...this.selectedValues]);
+      }
+    }
+  }
+
+  clearSelection(event: Event): void {
+    event.stopPropagation();
+    
+    if (this.multiple) {
+      this.selectedValues = [];
+      this.onChange([]);
+      this.selectionChange.emit([]);
+    } else {
+      this.selectedValue = null;
+      this.onChange(null);
+      this.selectionChange.emit(null);
+    }
+  }
+
+  onKeyDown(event: KeyboardEvent): void {
+    switch (event.key) {
+      case 'Enter':
+      case ' ':
+        event.preventDefault();
+        if (!this.isOpen) {
+          this.openDropdown();
+        } else if (this.highlightedIndex >= 0) {
+          const option = this.filteredOptions[this.highlightedIndex];
+          if (option && !option.disabled && !option.isGroup) {
+            this.selectOption(option);
+          }
+        }
+        break;
+        
+      case 'Escape':
+        event.preventDefault();
+        this.closeDropdown();
+        break;
+        
+      case 'ArrowDown':
+        event.preventDefault();
+        if (!this.isOpen) {
+          this.openDropdown();
+        } else {
+          this.highlightNext();
+        }
+        break;
+        
+      case 'ArrowUp':
+        event.preventDefault();
+        if (this.isOpen) {
+          this.highlightPrevious();
+        }
+        break;
+
+      case 'Home':
+        if (this.isOpen) {
+          event.preventDefault();
+          this.highlightFirst();
+        }
+        break;
+
+      case 'End':
+        if (this.isOpen) {
+          event.preventDefault();
+          this.highlightLast();
+        }
+        break;
+        
+      case 'Tab':
+        if (this.isOpen) {
+          this.closeDropdown();
+        }
+        break;
+
+      // 字母键快速导航
+      default:
+        if (this.isOpen && event.key.length === 1 && /[a-zA-Z0-9]/.test(event.key)) {
+          this.quickNavigate(event.key.toLowerCase());
+        }
+        break;
+    }
+  }
+
+  private highlightNext(): void {
+    const maxIndex = this.filteredOptions.length - 1;
+    let nextIndex = this.highlightedIndex < maxIndex ? this.highlightedIndex + 1 : 0;
+    
+    // 跳过禁用和分组标题选项
+    while (nextIndex !== this.highlightedIndex) {
+      const option = this.filteredOptions[nextIndex];
+      if (option && !option.disabled && !option.isGroup) {
+        this.highlightedIndex = nextIndex;
+        this.scrollToHighlighted();
+        return;
+      }
+      nextIndex = nextIndex < maxIndex ? nextIndex + 1 : 0;
+    }
+  }
+
+  private highlightPrevious(): void {
+    const maxIndex = this.filteredOptions.length - 1;
+    let prevIndex = this.highlightedIndex > 0 ? this.highlightedIndex - 1 : maxIndex;
+    
+    // 跳过禁用和分组标题选项
+    while (prevIndex !== this.highlightedIndex) {
+      const option = this.filteredOptions[prevIndex];
+      if (option && !option.disabled && !option.isGroup) {
+        this.highlightedIndex = prevIndex;
+        this.scrollToHighlighted();
+        return;
+      }
+      prevIndex = prevIndex > 0 ? prevIndex - 1 : maxIndex;
+    }
+  }
+
+  private highlightFirst(): void {
+    for (let i = 0; i < this.filteredOptions.length; i++) {
+      const option = this.filteredOptions[i];
+      if (option && !option.disabled && !option.isGroup) {
+        this.highlightedIndex = i;
+        this.scrollToHighlighted();
+        return;
+      }
+    }
+  }
+
+  private highlightLast(): void {
+    for (let i = this.filteredOptions.length - 1; i >= 0; i--) {
+      const option = this.filteredOptions[i];
+      if (option && !option.disabled && !option.isGroup) {
+        this.highlightedIndex = i;
+        this.scrollToHighlighted();
+        return;
+      }
+    }
+  }
+
+  private quickNavigate(key: string): void {
+    const startIndex = this.highlightedIndex + 1;
+    
+    // 从当前位置之后开始搜索
+    for (let i = startIndex; i < this.filteredOptions.length; i++) {
+      const option = this.filteredOptions[i];
+      if (option && !option.disabled && !option.isGroup && 
+          option.label.toLowerCase().startsWith(key)) {
+        this.highlightedIndex = i;
+        this.scrollToHighlighted();
+        return;
+      }
+    }
+    
+    // 如果没找到,从头开始搜索
+    for (let i = 0; i < startIndex; i++) {
+      const option = this.filteredOptions[i];
+      if (option && !option.disabled && !option.isGroup && 
+          option.label.toLowerCase().startsWith(key)) {
+        this.highlightedIndex = i;
+        this.scrollToHighlighted();
+        return;
+      }
+    }
+  }
+
+  private scrollToHighlighted(): void {
+    if (this.highlightedIndex >= 0 && this.optionsContainer) {
+      const optionElements = this.optionsContainer.nativeElement.querySelectorAll('.dropdown-option:not(.group-header)');
+      const highlightedElement = optionElements[this.highlightedIndex];
+      
+      if (highlightedElement) {
+        highlightedElement.scrollIntoView({
+          block: 'nearest',
+          behavior: 'smooth'
+        });
+      }
+    }
+  }
+
+  getOptionIndex(option: DropdownOption): number {
+    return this.filteredOptions.indexOf(option);
+  }
+
+  private updateGroupedOptions(): void {
+    this.groupedOptions.clear();
+    
+    this.filteredOptions.forEach(option => {
+      if (option.group) {
+        if (!this.groupedOptions.has(option.group)) {
+          this.groupedOptions.set(option.group, []);
+        }
+        this.groupedOptions.get(option.group)!.push(option);
+      }
+    });
+  }
+
+  // 点击外部关闭
+  private clickOutsideHandler = (event: Event) => {
+    if (!this.elementRef.nativeElement.contains(event.target)) {
+      this.close();
+    }
+  };
+
+  private setupClickOutside(): void {
+    document.addEventListener('click', this.clickOutsideHandler);
+  }
+
+  private removeClickOutside(): void {
+    document.removeEventListener('click', this.clickOutsideHandler);
+  }
+}

+ 300 - 0
src/app/services/doubao-ai.service.ts

@@ -0,0 +1,300 @@
+import { Injectable } from '@angular/core';
+import { HttpClient, HttpHeaders } from '@angular/common/http';
+import { Observable, throwError } from 'rxjs';
+import { catchError, map } from 'rxjs/operators';
+
+export interface ResumeAnalysisRequest {
+  resumeText: string;
+  jobPosition: string;
+  jobRequirements: string[];
+}
+
+export interface ResumeAnalysisResponse {
+  matchDimensions: MatchDimension[];
+  recommendation: Recommendation;
+  screeningInfo: ScreeningInfo[];
+  overallScore: number;
+  analysisTime: Date;
+}
+
+export interface MatchDimension {
+  id: number;
+  name: string;
+  score: number;
+  level: 'high' | 'medium' | 'low';
+  icon: string;
+  description?: string;
+}
+
+export interface Recommendation {
+  title: string;
+  level: 'recommend' | 'consider' | 'reject';
+  levelText: string;
+  icon: string;
+  summary: string;
+  reasons: string[];
+  concerns: string[];
+  confidence: number;
+}
+
+export interface ScreeningInfo {
+  id: number;
+  title: string;
+  detail: string;
+  status: 'pass' | 'warning' | 'fail';
+  statusText: string;
+  icon: string;
+  score?: number;
+}
+
+@Injectable({
+  providedIn: 'root'
+})
+export class DoubaoAiService {
+  private readonly API_BASE_URL = 'https://ark.cn-beijing.volces.com/api/v3';
+  private readonly API_KEY = 'your-doubao-api-key'; // 需要配置实际的API密钥
+  private readonly MODEL_ID = 'ep-20241201234567-abcdef'; // 豆包模型ID
+
+  constructor(private http: HttpClient) {}
+
+  /**
+   * 分析简历内容
+   */
+  analyzeResume(request: ResumeAnalysisRequest): Observable<ResumeAnalysisResponse> {
+    const prompt = this.buildAnalysisPrompt(request);
+    
+    const headers = new HttpHeaders({
+      'Content-Type': 'application/json',
+      'Authorization': `Bearer ${this.API_KEY}`
+    });
+
+    const body = {
+      model: this.MODEL_ID,
+      messages: [
+        {
+          role: 'system',
+          content: '你是一个专业的HR助手,擅长简历分析和人才评估。请根据提供的简历内容和岗位要求,进行详细的匹配度分析。'
+        },
+        {
+          role: 'user',
+          content: prompt
+        }
+      ],
+      temperature: 0.7,
+      max_tokens: 2000
+    };
+
+    return this.http.post<any>(`${this.API_BASE_URL}/chat/completions`, body, { headers })
+      .pipe(
+        map(response => this.parseAnalysisResponse(response)),
+        catchError(error => {
+          console.error('豆包API调用失败:', error);
+          return throwError(() => new Error('简历分析服务暂时不可用,请稍后重试'));
+        })
+      );
+  }
+
+  /**
+   * 构建分析提示词
+   */
+  private buildAnalysisPrompt(request: ResumeAnalysisRequest): string {
+    return `
+请分析以下简历内容,并根据岗位要求进行匹配度评估:
+
+【岗位信息】
+职位:${request.jobPosition}
+要求:${request.jobRequirements.join('、')}
+
+【简历内容】
+${request.resumeText}
+
+请按照以下JSON格式返回分析结果:
+{
+  "overallScore": 85,
+  "matchDimensions": [
+    {
+      "id": 1,
+      "name": "技术能力",
+      "score": 90,
+      "level": "high",
+      "icon": "code",
+      "description": "具体评估说明"
+    }
+  ],
+  "recommendation": {
+    "title": "推荐结论标题",
+    "level": "recommend",
+    "levelText": "推荐",
+    "icon": "thumb_up",
+    "summary": "总体评估摘要",
+    "reasons": ["推荐原因1", "推荐原因2"],
+    "concerns": ["关注点1", "关注点2"],
+    "confidence": 85
+  },
+  "screeningInfo": [
+    {
+      "id": 1,
+      "title": "学历要求",
+      "detail": "具体信息",
+      "status": "pass",
+      "statusText": "符合",
+      "icon": "school",
+      "score": 90
+    }
+  ]
+}
+
+评估维度包括但不限于:
+1. 技术能力匹配度
+2. 工作经验相关性
+3. 教育背景适配性
+4. 项目经验质量
+5. 团队协作能力
+6. 学习成长潜力
+
+请确保返回的是有效的JSON格式,分数范围0-100,level为high/medium/low,status为pass/warning/fail。
+    `;
+  }
+
+  /**
+   * 解析API响应
+   */
+  private parseAnalysisResponse(response: any): ResumeAnalysisResponse {
+    try {
+      const content = response.choices[0].message.content;
+      const analysisData = JSON.parse(content);
+      
+      return {
+        ...analysisData,
+        analysisTime: new Date()
+      };
+    } catch (error) {
+      console.error('解析豆包API响应失败:', error);
+      // 返回默认分析结果
+      return this.getDefaultAnalysisResult();
+    }
+  }
+
+  /**
+   * 获取默认分析结果(当API调用失败时使用)
+   */
+  private getDefaultAnalysisResult(): ResumeAnalysisResponse {
+    return {
+      overallScore: 75,
+      analysisTime: new Date(),
+      matchDimensions: [
+        { id: 1, name: '技术能力', score: 80, level: 'high', icon: 'code' },
+        { id: 2, name: '工作经验', score: 75, level: 'medium', icon: 'work' },
+        { id: 3, name: '教育背景', score: 85, level: 'high', icon: 'school' },
+        { id: 4, name: '项目经验', score: 70, level: 'medium', icon: 'assignment' },
+        { id: 5, name: '团队协作', score: 65, level: 'medium', icon: 'groups' }
+      ],
+      recommendation: {
+        title: '建议进入面试环节',
+        level: 'consider',
+        levelText: '考虑',
+        icon: 'psychology',
+        summary: '候选人具备基本的岗位要求,建议进一步面试了解。',
+        reasons: [
+          '具备相关技术背景',
+          '有一定的项目经验',
+          '学习能力较强'
+        ],
+        concerns: [
+          '工作经验相对较少',
+          '需要进一步了解实际能力'
+        ],
+        confidence: 75
+      },
+      screeningInfo: [
+        { id: 1, title: '学历要求', detail: '本科及以上', status: 'pass', statusText: '符合', icon: 'school', score: 85 },
+        { id: 2, title: '工作经验', detail: '相关经验2年', status: 'warning', statusText: '基本符合', icon: 'work', score: 70 },
+        { id: 3, title: '技能匹配', detail: '核心技能匹配度75%', status: 'pass', statusText: '良好', icon: 'star', score: 75 },
+        { id: 4, title: '薪资期望', detail: '期望薪资合理', status: 'pass', statusText: '符合', icon: 'payments', score: 90 }
+      ]
+    };
+  }
+
+  /**
+   * 提取文件文本内容
+   */
+  extractTextFromFile(file: File): Promise<string> {
+    return new Promise((resolve, reject) => {
+      if (file.type === 'application/pdf') {
+        // PDF文件处理
+        this.extractPdfText(file).then(resolve).catch(reject);
+      } else if (file.type.includes('word') || file.type.includes('document')) {
+        // Word文档处理
+        this.extractWordText(file).then(resolve).catch(reject);
+      } else if (file.type === 'text/plain') {
+        // 纯文本文件
+        this.extractPlainText(file).then(resolve).catch(reject);
+      } else {
+        reject(new Error('不支持的文件格式'));
+      }
+    });
+  }
+
+  /**
+   * 提取PDF文本
+   */
+  private extractPdfText(file: File): Promise<string> {
+    // 这里需要使用PDF.js或其他PDF解析库
+    // 简化实现,实际项目中需要集成PDF解析功能
+    return new Promise((resolve) => {
+      const reader = new FileReader();
+      reader.onload = () => {
+        // 模拟PDF文本提取
+        resolve(`从PDF文件 "${file.name}" 中提取的简历内容:
+        
+姓名:张三
+学历:本科
+专业:计算机科学与技术
+工作经验:3年前端开发经验
+技能:JavaScript, TypeScript, Angular, React, Vue.js
+项目经验:参与多个企业级前端项目开发
+        `);
+      };
+      reader.readAsArrayBuffer(file);
+    });
+  }
+
+  /**
+   * 提取Word文档文本
+   */
+  private extractWordText(file: File): Promise<string> {
+    // 这里需要使用mammoth.js或其他Word解析库
+    return new Promise((resolve) => {
+      const reader = new FileReader();
+      reader.onload = () => {
+        // 模拟Word文档文本提取
+        resolve(`从Word文档 "${file.name}" 中提取的简历内容:
+        
+个人信息:李四
+教育背景:硕士研究生
+专业方向:软件工程
+工作经历:5年全栈开发经验
+核心技能:Java, Spring Boot, MySQL, Redis
+项目成果:主导开发多个大型系统
+        `);
+      };
+      reader.readAsArrayBuffer(file);
+    });
+  }
+
+  /**
+   * 提取纯文本
+   */
+  private extractPlainText(file: File): Promise<string> {
+    return new Promise((resolve, reject) => {
+      const reader = new FileReader();
+      reader.onload = () => {
+        resolve(reader.result as string);
+      };
+      reader.onerror = () => {
+        reject(new Error('文件读取失败'));
+      };
+      reader.readAsText(file, 'UTF-8');
+    });
+  }
+}

Некоторые файлы не были показаны из-за большого количества измененных файлов