徐福静0235668 2 недель назад
Родитель
Сommit
0cea583a2f

+ 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' }

+ 265 - 201
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,29 +434,15 @@
                         </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>
     </div>
   }
 
@@ -447,6 +479,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 +592,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 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-stats">
-                      <div class="stat-item">
-                        <mat-icon>people</mat-icon>
-                        <span>{{stage.candidateCount}}人</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="stat-item">
-                        <mat-icon>percent</mat-icon>
-                        <span>通过率 {{stage.passRate}}%</span>
-                      </div>
-                    </div>
-                    <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 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>
 
             <!-- 试用期跟踪关联入口 -->

+ 1175 - 99
src/app/pages/hr/dashboard/dashboard.scss

@@ -1,15 +1,15 @@
 .hr-dashboard-container {
-  padding: 20px;
+  padding: 16px;
   background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
   min-height: 100vh;
   font-family: 'Roboto', sans-serif;
 
   .top-navigation {
-    margin-bottom: 30px;
+    margin-bottom: 20px;
     
     .nav-buttons {
       display: flex;
-      gap: 15px;
+      gap: 12px;
       justify-content: center;
       flex-wrap: wrap;
       
@@ -40,26 +40,36 @@
 
   // 数据可视化页面样式
   .visualization-page {
-    .main-layout {
-      display: grid;
-      grid-template-columns: 2fr 1fr;
-      gap: 30px;
-      min-height: 80vh;
+    display: flex;
+    flex-direction: column;
+    gap: 20px;
 
+    // 顶部三个图表一排显示
+    .top-charts-row {
+      display: grid;
+      grid-template-columns: 1fr 1fr 1fr;
+      gap: 16px;
+      
       @media (max-width: 1200px) {
+        grid-template-columns: 1fr 1fr;
+        .chart-card:last-child {
+          grid-column: 1 / -1;
+        }
+      }
+      
+      @media (max-width: 768px) {
         grid-template-columns: 1fr;
-        gap: 20px;
       }
     }
 
-    .left-panel {
-      display: grid;
-      grid-template-columns: 1fr 1fr;
-      gap: 20px;
+    // 离职原因分析区域占满整行
+    .resignation-analysis-section {
+      width: 100%;
       
-      @media (max-width: 768px) {
-        grid-template-columns: 1fr;
+      .analysis-card {
+        width: 100%;
       }
+    }
 
       .chart-card {
         background: rgba(255, 255, 255, 0.95);
@@ -78,7 +88,7 @@
           background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
           color: white;
           border-radius: 20px 20px 0 0;
-          padding: 20px;
+          padding: 16px;
 
           mat-card-title {
             display: flex;
@@ -100,7 +110,7 @@
         }
 
         mat-card-content {
-          padding: 25px;
+          padding: 20px;
         }
       }
 
@@ -610,12 +620,12 @@
       }
       
       mat-card-content {
-        padding: 24px;
+        padding: 20px;
         
         .comparison-selectors {
           display: flex;
-          gap: 24px;
-          margin-bottom: 24px;
+          gap: 20px;
+          margin-bottom: 20px;
           flex-wrap: wrap;
           
           .selector-group {
@@ -911,8 +921,8 @@
         
         // 对比图表样式
         .comparison-chart-section {
-          margin-top: 32px;
-          padding-top: 24px;
+          margin-top: 24px;
+          padding-top: 20px;
           border-top: 1px solid #e9ecef;
           
           .chart-header {
@@ -1163,7 +1173,7 @@
         .resignation-reasons-analysis {
           display: grid;
           grid-template-columns: 1fr 1fr;
-          gap: 32px;
+          gap: 24px;
           margin-bottom: 32px;
           
           @media (max-width: 1200px) {
@@ -1213,6 +1223,31 @@
               font-weight: 600;
             }
             
+            .reasons-list-container {
+              max-height: 480px; // 限制高度,大约显示两个项目
+              overflow-y: auto;
+              padding-right: 8px;
+              
+              // 自定义滚动条样式
+              &::-webkit-scrollbar {
+                width: 6px;
+              }
+              
+              &::-webkit-scrollbar-track {
+                background: #f1f1f1;
+                border-radius: 3px;
+              }
+              
+              &::-webkit-scrollbar-thumb {
+                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+                border-radius: 3px;
+                
+                &:hover {
+                  background: linear-gradient(135deg, #5a67d8 0%, #6b46c1 100%);
+                }
+              }
+            }
+            
             .reasons-list {
               .reason-item {
                 background: #f8f9fa;
@@ -1415,41 +1450,7 @@
           }
         }
         
-        .resignation-trends {
-          .trends-header {
-            display: flex;
-            justify-content: space-between;
-            align-items: center;
-            margin-bottom: 20px;
-            
-            h3 {
-              margin: 0;
-              color: #333;
-              font-weight: 600;
-            }
-            
-            mat-button-toggle-group {
-              .mat-button-toggle {
-                border: 1px solid #e0e0e0;
-                
-                &.mat-button-toggle-checked {
-                  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-                  color: white;
-                }
-              }
-            }
-          }
-          
-          .trends-chart-container {
-            background: #f8f9fa;
-            border-radius: 12px;
-            padding: 20px;
-            display: flex;
-            justify-content: center;
-            align-items: center;
-            min-height: 300px;
-          }
-        }
+
       }
     }
   }
@@ -1613,7 +1614,9 @@
 
   // 响应式设计
   @media (max-width: 768px) {
-    padding: 15px;
+    .hr-dashboard-container {
+      padding: 15px;
+    }
 
     .top-navigation {
       margin-bottom: 20px;
@@ -1627,11 +1630,8 @@
     }
 
     .visualization-page {
-      .main-layout {
-        gap: 15px;
-      }
-
-      .left-panel {
+      .top-charts-row {
+        flex-direction: column;
         gap: 15px;
 
         .chart-card {
@@ -1648,6 +1648,10 @@
           }
         }
       }
+
+      .resignation-analysis-section {
+        margin-top: 15px;
+      }
     }
   }
 
@@ -1668,11 +1672,40 @@
       }
     }
 
+    .stages-timeline-container {
+      position: relative;
+      margin: 20px 0;
+    }
+
     .stages-timeline {
       display: flex;
       flex-direction: column;
       gap: 20px;
-      margin: 20px 0;
+      max-height: 480px; // 限制显示约2个项目的高度
+      overflow-y: auto;
+      padding-right: 10px;
+      
+      // 自定义滚动条样式
+      &::-webkit-scrollbar {
+        width: 6px;
+      }
+      
+      &::-webkit-scrollbar-track {
+        background: rgba(0, 0, 0, 0.1);
+        border-radius: 3px;
+      }
+      
+      &::-webkit-scrollbar-thumb {
+        background: linear-gradient(135deg, #2196f3, #03a9f4);
+        border-radius: 3px;
+        
+        &:hover {
+          background: linear-gradient(135deg, #1976d2, #0288d1);
+        }
+      }
+      
+      // 滚动动画
+      scroll-behavior: smooth;
 
       .timeline-item {
         display: flex;
@@ -1860,6 +1893,61 @@
       }
     }
 
+    // 滑动指示器样式
+    .scroll-indicator {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-top: 20px;
+      padding: 15px 20px;
+      background: rgba(255, 255, 255, 0.8);
+      border-radius: 12px;
+      border: 1px solid rgba(0, 0, 0, 0.1);
+
+      .scroll-dots {
+        display: flex;
+        gap: 8px;
+
+        .scroll-dot {
+          width: 8px;
+          height: 8px;
+          border-radius: 50%;
+          background: rgba(0, 0, 0, 0.3);
+          cursor: pointer;
+          transition: all 0.3s ease;
+
+          &.active {
+            background: linear-gradient(135deg, #2196f3, #03a9f4);
+            transform: scale(1.2);
+          }
+
+          &:hover {
+            background: rgba(33, 150, 243, 0.6);
+            transform: scale(1.1);
+          }
+        }
+      }
+
+      .scroll-hint {
+        display: flex;
+        align-items: center;
+        gap: 8px;
+        color: #666;
+        font-size: 14px;
+
+        mat-icon {
+          font-size: 18px;
+          width: 18px;
+          height: 18px;
+          color: #2196f3;
+        }
+
+        span {
+          font-weight: 500;
+        }
+      }
+    }
+
     .probation-link-section {
       margin-top: 30px;
 
@@ -1953,66 +2041,560 @@
         margin-bottom: 20px;
         
         .upload-zone {
-          border: 2px dashed #ddd;
-          border-radius: 10px;
-          padding: 40px 20px;
+          background: linear-gradient(135deg, rgba(255, 255, 255, 0.9) 0%, rgba(248, 250, 252, 0.9) 100%);
+          border: 2px dashed rgba(59, 130, 246, 0.3);
+          border-radius: 20px;
+          padding: 32px 24px;
           text-align: center;
           cursor: pointer;
-          transition: all 0.3s ease;
+          transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
+          backdrop-filter: blur(10px);
+          position: relative;
+          overflow: hidden;
+          
+          &::before {
+            content: '';
+            position: absolute;
+            top: 0;
+            left: 0;
+            right: 0;
+            bottom: 0;
+            background: linear-gradient(135deg, rgba(59, 130, 246, 0.05) 0%, rgba(147, 51, 234, 0.05) 100%);
+            opacity: 0;
+            transition: opacity 0.3s ease;
+          }
           
           &:hover {
-            border-color: #2196F3;
-            background: rgba(33, 150, 243, 0.05);
+            border-color: rgba(59, 130, 246, 0.6);
+            transform: translateY(-2px);
+            box-shadow: 0 20px 40px rgba(59, 130, 246, 0.15);
+            
+            &::before {
+              opacity: 1;
+            }
+            
+            .upload-icon {
+              transform: scale(1.1);
+              color: #3b82f6;
+            }
+            
+            .upload-title {
+              color: #1e40af;
+            }
+          }
+          
+          &.drag-over {
+            border-color: #3b82f6;
+            background: linear-gradient(135deg, rgba(59, 130, 246, 0.1) 0%, rgba(147, 51, 234, 0.1) 100%);
+            transform: scale(1.02);
+            
+            .upload-icon {
+              animation: bounce 0.6s ease-in-out;
+              color: #3b82f6;
+            }
           }
           
           .upload-icon {
-            font-size: 48px;
-            color: #2196F3;
-            margin-bottom: 10px;
+            font-size: 56px;
+            background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%);
+            -webkit-background-clip: text;
+            -webkit-text-fill-color: transparent;
+            background-clip: text;
+            margin-bottom: 16px;
+            transition: all 0.3s ease;
           }
           
-          p {
-            margin: 5px 0;
-            
-            &.upload-hint {
-              font-size: 12px;
-              color: #666;
+          .upload-title {
+            font-size: 18px;
+            font-weight: 600;
+            color: #1f2937;
+            margin: 8px 0;
+            transition: color 0.3s ease;
+          }
+          
+          .upload-hint {
+            font-size: 14px;
+            color: #6b7280;
+            margin: 8px 0 24px 0;
+            line-height: 1.5;
+          }
+          
+          .upload-actions {
+            .mat-stroked-button {
+              background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%);
+              color: white;
+              border: none;
+              border-radius: 12px;
+              padding: 12px 24px;
+              font-weight: 600;
+              transition: all 0.3s ease;
+              box-shadow: 0 4px 15px rgba(59, 130, 246, 0.3);
+              
+              &:hover {
+                transform: translateY(-2px);
+                box-shadow: 0 8px 25px rgba(59, 130, 246, 0.4);
+              }
+              
+              mat-icon {
+                margin-right: 8px;
+              }
             }
           }
         }
       }
 
-      .analysis-results {
-        .skills-match {
-          h4 {
-            margin-bottom: 15px;
+      @keyframes bounce {
+        0%, 20%, 50%, 80%, 100% {
+          transform: translateY(0);
+        }
+        40% {
+          transform: translateY(-10px);
+        }
+        60% {
+          transform: translateY(-5px);
+        }
+      }
+
+      .analysis-progress {
+        background: rgba(255, 255, 255, 0.95);
+        border-radius: 15px;
+        padding: 16px;
+        margin: 16px 0;
+        box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
+        border: 1px solid rgba(33, 150, 243, 0.2);
+        
+        .progress-header {
+          display: flex;
+          align-items: center;
+          margin-bottom: 15px;
+          
+          .analyzing-icon {
+            color: #2196F3;
+            margin-right: 10px;
+            animation: pulse 2s infinite;
+          }
+          
+          .progress-text {
+            font-weight: 500;
             color: #333;
           }
+        }
+        
+        mat-progress-bar {
+          margin-bottom: 10px;
+          height: 8px;
+          border-radius: 4px;
+          
+          ::ng-deep .mat-progress-bar-fill::after {
+            background: linear-gradient(45deg, #2196F3, #21CBF3);
+          }
+        }
+        
+        .progress-details {
+          display: flex;
+          justify-content: space-between;
+          align-items: center;
+          font-size: 14px;
+          
+          .file-name {
+            color: #666;
+            max-width: 200px;
+            overflow: hidden;
+            text-overflow: ellipsis;
+            white-space: nowrap;
+          }
+          
+          .progress-percentage {
+            color: #2196F3;
+            font-weight: 600;
+          }
+        }
+      }
+
+      @keyframes pulse {
+        0% { transform: scale(1); }
+        50% { transform: scale(1.1); }
+        100% { transform: scale(1); }
+      }
+
+      .analysis-results {
+        animation: slideInUp 0.6s ease-out;
+        
+        h4 {
+          display: flex;
+          align-items: center;
+          font-size: 18px;
+          font-weight: 600;
+          color: #1f2937;
+          margin: 24px 0 16px 0;
+          
+          mat-icon {
+            margin-right: 12px;
+            color: #3b82f6;
+            font-size: 24px;
+          }
+        }
+        
+        .match-dimensions {
+          margin-bottom: 24px;
           
-          .skill-item {
+          .dimension-tags {
             display: flex;
-            align-items: center;
-            gap: 15px;
-            margin-bottom: 10px;
+            flex-wrap: wrap;
+            gap: 10px;
             
-            span:first-child {
-              min-width: 80px;
-              font-weight: 500;
+            mat-chip-set {
+              mat-chip {
+                background: rgba(255, 255, 255, 0.9);
+                border: 1px solid rgba(59, 130, 246, 0.2);
+                border-radius: 16px;
+                padding: 12px 16px;
+                font-weight: 500;
+                transition: all 0.3s ease;
+                backdrop-filter: blur(10px);
+                
+                &:hover {
+                  transform: translateY(-2px);
+                  box-shadow: 0 8px 25px rgba(59, 130, 246, 0.15);
+                }
+                
+                &.match-level-high {
+                  background: linear-gradient(135deg, rgba(34, 197, 94, 0.1) 0%, rgba(16, 185, 129, 0.1) 100%);
+                  border-color: rgba(34, 197, 94, 0.3);
+                  color: #059669;
+                  
+                  mat-icon {
+                    color: #10b981;
+                  }
+                  
+                  .match-score {
+                    background: rgba(34, 197, 94, 0.2);
+                    color: #065f46;
+                  }
+                }
+                
+                &.match-level-medium {
+                  background: linear-gradient(135deg, rgba(251, 191, 36, 0.1) 0%, rgba(245, 158, 11, 0.1) 100%);
+                  border-color: rgba(251, 191, 36, 0.3);
+                  color: #d97706;
+                  
+                  mat-icon {
+                    color: #f59e0b;
+                  }
+                  
+                  .match-score {
+                    background: rgba(251, 191, 36, 0.2);
+                    color: #92400e;
+                  }
+                }
+                
+                &.match-level-low {
+                  background: linear-gradient(135deg, rgba(239, 68, 68, 0.1) 0%, rgba(220, 38, 38, 0.1) 100%);
+                  border-color: rgba(239, 68, 68, 0.3);
+                  color: #dc2626;
+                  
+                  mat-icon {
+                    color: #ef4444;
+                  }
+                  
+                  .match-score {
+                    background: rgba(239, 68, 68, 0.2);
+                    color: #991b1b;
+                  }
+                }
+                
+                mat-icon {
+                  margin-right: 8px;
+                  font-size: 18px;
+                }
+                
+                .match-score {
+                  margin-left: 8px;
+                  padding: 4px 8px;
+                  border-radius: 8px;
+                  font-size: 12px;
+                  font-weight: 600;
+                }
+              }
+            }
+          }
+        }
+        
+        .recommendation-section {
+          margin-bottom: 24px;
+          
+          .recommendation-card {
+            background: rgba(255, 255, 255, 0.95);
+            border-radius: 20px;
+            padding: 20px;
+            backdrop-filter: blur(10px);
+            border: 1px solid rgba(255, 255, 255, 0.2);
+            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
+            transition: all 0.3s ease;
+            
+            &:hover {
+              transform: translateY(-2px);
+              box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
             }
             
-            mat-progress-bar {
-              flex: 1;
+            &.recommendation-recommend {
+              border-left: 4px solid #10b981;
+              background: linear-gradient(135deg, rgba(16, 185, 129, 0.05) 0%, rgba(34, 197, 94, 0.05) 100%);
             }
             
-            span:last-child {
-              min-width: 40px;
-              text-align: right;
-              font-weight: 600;
-              color: #2196F3;
+            &.recommendation-consider {
+              border-left: 4px solid #f59e0b;
+              background: linear-gradient(135deg, rgba(245, 158, 11, 0.05) 0%, rgba(251, 191, 36, 0.05) 100%);
+            }
+            
+            &.recommendation-reject {
+              border-left: 4px solid #ef4444;
+              background: linear-gradient(135deg, rgba(239, 68, 68, 0.05) 0%, rgba(220, 38, 38, 0.05) 100%);
+            }
+            
+            .recommendation-header {
+              display: flex;
+              align-items: center;
+              margin-bottom: 16px;
+              
+              mat-icon {
+                font-size: 24px;
+                margin-right: 12px;
+                
+                .recommendation-recommend & {
+                  color: #10b981;
+                }
+                
+                .recommendation-consider & {
+                  color: #f59e0b;
+                }
+                
+                .recommendation-reject & {
+                  color: #ef4444;
+                }
+              }
+              
+              .recommendation-title {
+                flex: 1;
+                font-size: 18px;
+                font-weight: 600;
+                color: #1f2937;
+              }
+              
+              mat-chip {
+                border-radius: 12px;
+                font-weight: 600;
+                
+                &.level-recommend {
+                  background: rgba(16, 185, 129, 0.2);
+                  color: #065f46;
+                }
+                
+                &.level-consider {
+                  background: rgba(245, 158, 11, 0.2);
+                  color: #92400e;
+                }
+                
+                &.level-reject {
+                  background: rgba(239, 68, 68, 0.2);
+                  color: #991b1b;
+                }
+              }
+            }
+            
+            .recommendation-content {
+              .recommendation-summary {
+                font-size: 16px;
+                line-height: 1.6;
+                color: #4b5563;
+                margin-bottom: 20px;
+                padding: 16px;
+                background: rgba(248, 250, 252, 0.8);
+                border-radius: 12px;
+                border-left: 3px solid #3b82f6;
+              }
+              
+              .recommendation-reasons,
+              .recommendation-concerns {
+                margin-bottom: 16px;
+                
+                h5 {
+                  font-size: 14px;
+                  font-weight: 600;
+                  color: #374151;
+                  margin-bottom: 8px;
+                  display: flex;
+                  align-items: center;
+                  
+                  &::before {
+                    content: '';
+                    width: 4px;
+                    height: 16px;
+                    background: #3b82f6;
+                    border-radius: 2px;
+                    margin-right: 8px;
+                  }
+                }
+                
+                ul {
+                  list-style: none;
+                  padding: 0;
+                  margin: 0;
+                  
+                  li {
+                    padding: 8px 0;
+                    padding-left: 24px;
+                    position: relative;
+                    color: #6b7280;
+                    line-height: 1.5;
+                    
+                    &::before {
+                      content: '•';
+                      position: absolute;
+                      left: 8px;
+                      color: #3b82f6;
+                      font-weight: bold;
+                    }
+                  }
+                }
+              }
+              
+              .recommendation-concerns {
+                h5::before {
+                  background: #f59e0b;
+                }
+                
+                ul li::before {
+                  color: #f59e0b;
+                }
+              }
+            }
+          }
+        }
+        
+        .screening-info {
+          .screening-list {
+            display: flex;
+            flex-direction: column;
+            gap: 12px;
+            
+            .screening-item {
+              display: flex;
+              align-items: center;
+              padding: 16px 20px;
+              background: rgba(255, 255, 255, 0.9);
+              border-radius: 16px;
+              backdrop-filter: blur(10px);
+              border: 1px solid rgba(255, 255, 255, 0.2);
+              transition: all 0.3s ease;
+              
+              &:hover {
+                transform: translateX(4px);
+                box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
+              }
+              
+              .screening-icon {
+                width: 40px;
+                height: 40px;
+                border-radius: 12px;
+                display: flex;
+                align-items: center;
+                justify-content: center;
+                margin-right: 16px;
+                
+                mat-icon {
+                  font-size: 20px;
+                }
+              }
+              
+              .screening-content {
+                flex: 1;
+                
+                .screening-title {
+                  font-weight: 600;
+                  color: #1f2937;
+                  margin-bottom: 4px;
+                }
+                
+                .screening-detail {
+                  font-size: 14px;
+                  color: #6b7280;
+                }
+              }
+              
+              .screening-status {
+                mat-chip {
+                  border-radius: 12px;
+                  font-weight: 600;
+                  font-size: 12px;
+                }
+              }
+              
+              &.status-pass {
+                border-left: 4px solid #10b981;
+                
+                .screening-icon {
+                  background: rgba(16, 185, 129, 0.1);
+                  
+                  mat-icon {
+                    color: #10b981;
+                  }
+                }
+                
+                .screening-status mat-chip {
+                  background: rgba(16, 185, 129, 0.2);
+                  color: #065f46;
+                }
+              }
+              
+              &.status-warning {
+                border-left: 4px solid #f59e0b;
+                
+                .screening-icon {
+                  background: rgba(245, 158, 11, 0.1);
+                  
+                  mat-icon {
+                    color: #f59e0b;
+                  }
+                }
+                
+                .screening-status mat-chip {
+                  background: rgba(245, 158, 11, 0.2);
+                  color: #92400e;
+                }
+              }
+              
+              &.status-fail {
+                border-left: 4px solid #ef4444;
+                
+                .screening-icon {
+                  background: rgba(239, 68, 68, 0.1);
+                  
+                  mat-icon {
+                    color: #ef4444;
+                  }
+                }
+                
+                .screening-status mat-chip {
+                  background: rgba(239, 68, 68, 0.2);
+                  color: #991b1b;
+                }
+              }
             }
           }
         }
       }
+
+      @keyframes slideInUp {
+        from {
+          opacity: 0;
+          transform: translateY(30px);
+        }
+        to {
+          opacity: 1;
+          transform: translateY(0);
+        }
+      }
     }
 
     .recruitment-stages {
@@ -2924,4 +3506,498 @@
        box-shadow: 0 0 0 0 rgba(33, 150, 243, 0);
      }
    }
-}
+
+   // iOS风格悬浮待办事项按钮
+   .ios-floating-todo-container {
+     position: fixed;
+     bottom: 32px;
+     right: 32px;
+     z-index: 1000;
+
+     .ios-floating-todo-btn {
+       width: 64px;
+       height: 64px;
+       border-radius: 50%;
+       border: none;
+       background: linear-gradient(135deg, #007AFF 0%, #5856D6 100%);
+       color: white;
+       box-shadow: 0 8px 32px rgba(0, 122, 255, 0.3);
+       transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
+       cursor: pointer;
+       position: relative;
+       display: flex;
+       align-items: center;
+       justify-content: center;
+
+       &:hover {
+         transform: translateY(-4px) scale(1.05);
+         box-shadow: 0 16px 48px rgba(0, 122, 255, 0.4);
+       }
+
+       &:active {
+         transform: translateY(-2px) scale(1.02);
+       }
+
+       .ios-btn-content {
+         display: flex;
+         align-items: center;
+         justify-content: center;
+         position: relative;
+
+         mat-icon {
+           font-size: 28px;
+           width: 28px;
+           height: 28px;
+         }
+       }
+
+       .ios-todo-badge {
+         position: absolute;
+         top: -8px;
+         right: -8px;
+         background: #FF3B30;
+         color: white;
+         border-radius: 50%;
+         width: 24px;
+         height: 24px;
+         display: flex;
+         align-items: center;
+         justify-content: center;
+         font-size: 12px;
+         font-weight: 700;
+         border: 3px solid white;
+         box-shadow: 0 2px 8px rgba(255, 59, 48, 0.3);
+       }
+     }
+
+     // 背景遮罩
+     .ios-panel-backdrop {
+       position: fixed;
+       top: 0;
+       left: 0;
+       width: 100vw;
+       height: 100vh;
+       background: rgba(0, 0, 0, 0.4);
+       backdrop-filter: blur(8px);
+       opacity: 0;
+       visibility: hidden;
+       transition: all 0.3s ease;
+       z-index: 998;
+
+       &.visible {
+         opacity: 1;
+         visibility: visible;
+       }
+     }
+
+     // iOS风格待办事项面板
+     .ios-todo-panel {
+       position: fixed;
+       top: 0;
+       right: -420px;
+       width: 420px;
+       height: 100vh;
+       background: rgba(255, 255, 255, 0.95);
+       backdrop-filter: blur(20px);
+       -webkit-backdrop-filter: blur(20px);
+       border-left: 1px solid rgba(255, 255, 255, 0.2);
+       transition: right 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
+       z-index: 999;
+       display: flex;
+       flex-direction: column;
+       overflow: hidden;
+
+       &.open {
+         right: 0;
+       }
+
+       // 面板头部
+       .ios-panel-header {
+         position: relative;
+         padding: 60px 24px 20px;
+         background: linear-gradient(135deg, rgba(0, 122, 255, 0.1) 0%, rgba(88, 86, 214, 0.1) 100%);
+         border-bottom: 1px solid rgba(0, 0, 0, 0.05);
+
+         .ios-header-blur {
+           position: absolute;
+           top: 0;
+           left: 0;
+           right: 0;
+           bottom: 0;
+           background: rgba(255, 255, 255, 0.8);
+           backdrop-filter: blur(20px);
+           -webkit-backdrop-filter: blur(20px);
+         }
+
+         .ios-header-content {
+           position: relative;
+           z-index: 1;
+           display: flex;
+           justify-content: space-between;
+           align-items: center;
+
+           h3 {
+             margin: 0;
+             color: #1D1D1F;
+             font-size: 28px;
+             font-weight: 700;
+             letter-spacing: -0.5px;
+           }
+
+           .ios-close-btn {
+             width: 36px;
+             height: 36px;
+             border-radius: 50%;
+             border: none;
+             background: rgba(0, 0, 0, 0.05);
+             color: #007AFF;
+             cursor: pointer;
+             transition: all 0.2s ease;
+             display: flex;
+             align-items: center;
+             justify-content: center;
+
+             &:hover {
+               background: rgba(0, 0, 0, 0.1);
+               transform: scale(1.1);
+             }
+
+             mat-icon {
+               font-size: 20px;
+               width: 20px;
+               height: 20px;
+             }
+           }
+         }
+       }
+
+       // 面板内容
+       .ios-panel-content {
+         flex: 1;
+         overflow-y: auto;
+         padding: 16px;
+         scroll-behavior: smooth;
+
+         &::-webkit-scrollbar {
+           width: 4px;
+         }
+
+         &::-webkit-scrollbar-track {
+           background: transparent;
+         }
+
+         &::-webkit-scrollbar-thumb {
+           background: rgba(0, 0, 0, 0.2);
+           border-radius: 2px;
+         }
+
+         // iOS风格待办事项
+         .ios-todo-item {
+           background: rgba(255, 255, 255, 0.9);
+           border-radius: 16px;
+           margin-bottom: 12px;
+           box-shadow: 0 2px 16px rgba(0, 0, 0, 0.08);
+           transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
+           border: 1px solid rgba(0, 0, 0, 0.05);
+           overflow: hidden;
+           display: flex;
+           position: relative;
+
+           &:hover {
+             transform: translateY(-2px);
+             box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
+           }
+
+           &.completed {
+             opacity: 0.7;
+             background: rgba(52, 199, 89, 0.05);
+             border-color: rgba(52, 199, 89, 0.2);
+
+             .ios-todo-title {
+               text-decoration: line-through;
+               color: #8E8E93;
+             }
+           }
+
+           &.in-progress {
+             border-left: 4px solid #FF9500;
+             background: rgba(255, 149, 0, 0.05);
+           }
+
+           // 拖拽手柄
+           .ios-drag-handle {
+             width: 24px;
+             display: flex;
+             align-items: center;
+             justify-content: center;
+             background: rgba(0, 0, 0, 0.02);
+             cursor: grab;
+             transition: background 0.2s ease;
+
+             &:hover {
+               background: rgba(0, 0, 0, 0.05);
+             }
+
+             &:active {
+               cursor: grabbing;
+             }
+
+             .ios-drag-dots {
+               display: flex;
+               flex-direction: column;
+               gap: 2px;
+
+               span {
+                 width: 3px;
+                 height: 3px;
+                 background: #C7C7CC;
+                 border-radius: 50%;
+               }
+             }
+           }
+
+           // 待办事项内容
+           .ios-todo-content {
+             flex: 1;
+             padding: 16px;
+
+             .ios-todo-header {
+               display: flex;
+               align-items: flex-start;
+               gap: 12px;
+               margin-bottom: 12px;
+
+               .ios-todo-icon {
+                 width: 40px;
+                 height: 40px;
+                 border-radius: 12px;
+                 display: flex;
+                 align-items: center;
+                 justify-content: center;
+                 flex-shrink: 0;
+
+                 &.type-resume {
+                   background: linear-gradient(135deg, #007AFF, #5856D6);
+                   color: white;
+                 }
+
+                 &.type-onboarding {
+                   background: linear-gradient(135deg, #34C759, #30D158);
+                   color: white;
+                 }
+
+                 &.type-resignation {
+                   background: linear-gradient(135deg, #FF3B30, #FF6B6B);
+                   color: white;
+                 }
+
+                 mat-icon {
+                   font-size: 20px;
+                   width: 20px;
+                   height: 20px;
+                 }
+               }
+
+               .ios-todo-title-section {
+                 flex: 1;
+
+                 .ios-todo-title {
+                   margin: 0 0 4px 0;
+                   color: #1D1D1F;
+                   font-size: 16px;
+                   font-weight: 600;
+                   line-height: 1.3;
+                 }
+
+                 .ios-todo-description {
+                   margin: 0;
+                   color: #8E8E93;
+                   font-size: 14px;
+                   line-height: 1.4;
+                 }
+               }
+             }
+
+             .ios-todo-meta {
+               display: flex;
+               gap: 12px;
+               margin-bottom: 16px;
+
+               .ios-priority-indicator {
+                 display: flex;
+                 align-items: center;
+                 gap: 6px;
+                 padding: 4px 8px;
+                 border-radius: 8px;
+                 background: rgba(0, 0, 0, 0.05);
+
+                 .ios-priority-dot {
+                   width: 8px;
+                   height: 8px;
+                   border-radius: 50%;
+                 }
+
+                 .ios-priority-text {
+                   font-size: 12px;
+                   font-weight: 500;
+                   color: #8E8E93;
+                 }
+
+                 &.priority-high {
+                   background: rgba(255, 59, 48, 0.1);
+                   .ios-priority-dot { background: #FF3B30; }
+                   .ios-priority-text { color: #FF3B30; }
+                 }
+
+                 &.priority-medium {
+                   background: rgba(255, 149, 0, 0.1);
+                   .ios-priority-dot { background: #FF9500; }
+                   .ios-priority-text { color: #FF9500; }
+                 }
+
+                 &.priority-low {
+                   background: rgba(52, 199, 89, 0.1);
+                   .ios-priority-dot { background: #34C759; }
+                   .ios-priority-text { color: #34C759; }
+                 }
+               }
+
+               .ios-status-indicator {
+                 padding: 4px 8px;
+                 border-radius: 8px;
+                 font-size: 12px;
+                 font-weight: 500;
+
+                 &.status-pending {
+                   background: rgba(142, 142, 147, 0.1);
+                   color: #8E8E93;
+                 }
+
+                 &.status-in_progress {
+                   background: rgba(255, 149, 0, 0.1);
+                   color: #FF9500;
+                 }
+
+                 &.status-completed {
+                   background: rgba(52, 199, 89, 0.1);
+                   color: #34C759;
+                 }
+               }
+             }
+
+             .ios-todo-actions {
+               display: flex;
+               gap: 8px;
+
+               .ios-action-btn {
+                 display: flex;
+                 align-items: center;
+                 gap: 6px;
+                 padding: 8px 12px;
+                 border-radius: 12px;
+                 border: none;
+                 font-size: 14px;
+                 font-weight: 500;
+                 cursor: pointer;
+                 transition: all 0.2s ease;
+
+                 mat-icon {
+                   font-size: 16px;
+                   width: 16px;
+                   height: 16px;
+                 }
+
+                 &.ios-complete-btn {
+                   background: rgba(52, 199, 89, 0.1);
+                   color: #34C759;
+
+                   &:hover {
+                     background: rgba(52, 199, 89, 0.2);
+                   }
+                 }
+
+                 &.ios-start-btn {
+                   background: rgba(0, 122, 255, 0.1);
+                   color: #007AFF;
+
+                   &:hover {
+                     background: rgba(0, 122, 255, 0.2);
+                   }
+                 }
+
+                 &.ios-reset-btn {
+                   background: rgba(142, 142, 147, 0.1);
+                   color: #8E8E93;
+
+                   &:hover {
+                     background: rgba(142, 142, 147, 0.2);
+                   }
+                 }
+               }
+             }
+           }
+         }
+
+         // 空状态
+         .ios-empty-state {
+           text-align: center;
+           padding: 60px 20px;
+           color: #8E8E93;
+
+           mat-icon {
+             font-size: 64px;
+             width: 64px;
+             height: 64px;
+             margin-bottom: 16px;
+             opacity: 0.5;
+           }
+
+           h4 {
+             margin: 0 0 8px 0;
+             font-size: 18px;
+             font-weight: 600;
+           }
+
+           p {
+             margin: 0;
+             font-size: 14px;
+           }
+         }
+       }
+
+       // 面板底部
+       .ios-panel-footer {
+         padding: 16px 24px 32px;
+         background: rgba(255, 255, 255, 0.9);
+         border-top: 1px solid rgba(0, 0, 0, 0.05);
+
+         .ios-add-todo-btn {
+           width: 100%;
+           padding: 16px;
+           border-radius: 12px;
+           border: none;
+           background: linear-gradient(135deg, #007AFF, #5856D6);
+           color: white;
+           font-size: 16px;
+           font-weight: 600;
+           cursor: pointer;
+           transition: all 0.2s ease;
+           display: flex;
+           align-items: center;
+           justify-content: center;
+           gap: 8px;
+
+           &:hover {
+             transform: translateY(-1px);
+             box-shadow: 0 4px 16px rgba(0, 122, 255, 0.3);
+           }
+
+           mat-icon {
+             font-size: 20px;
+             width: 20px;
+             height: 20px;
+           }
+         }
+       }
+     }
+   }

+ 623 - 34
src/app/pages/hr/dashboard/dashboard.ts

@@ -14,9 +14,12 @@ 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';
 
 // 数据模型定义
 export interface TodoItem {
@@ -152,7 +155,9 @@ export interface PerformanceMetric {
     MatInputModule,
     DragDropModule,
     MatTableModule,
-    MatButtonToggleModule
+    MatButtonToggleModule,
+    MatProgressSpinnerModule,
+    MatSnackBarModule
   ],
   templateUrl: './dashboard.html',
   styleUrls: ['./dashboard.scss'],
@@ -172,10 +177,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 +375,7 @@ export class Dashboard implements OnInit, AfterViewInit {
     // 延迟初始化图表,确保DOM已渲染
     setTimeout(() => {
       this.initializeCharts();
+      this.initScrollIndicator();
     }, 100);
   }
 
@@ -620,7 +635,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;
@@ -709,9 +723,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 +875,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 +894,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 +973,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 +1066,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 +1256,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 +1523,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 +1546,7 @@ export class Dashboard implements OnInit, AfterViewInit {
     this.initPieChart();
     this.initLineChart();
     this.initRadarChart();
+    this.initResignationChart();
   }
 
   // 初始化职级分布饼图
@@ -1316,12 +1748,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);
   }
 
   // 处理按钮按压效果

+ 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');
+    });
+  }
+}