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