| | |
| | | <template> |
| | | <div class="app-container analytics-container"> |
| | | <div class="app-container analytics-container" v-loading="loading"> |
| | | |
| | | <!-- 关键指标卡片 --> |
| | | <el-row :gutter="20" class="metrics-cards"> |
| | |
| | | <component :is="item.icon" /> |
| | | </el-icon> |
| | | </div> |
| | | <div class="card-info"> |
| | | <div class="card-info"> |
| | | <div class="card-number"> |
| | | <el-skeleton-item v-if="loading" variant="text" style="width: 60px; height: 32px;" /> |
| | | <span v-else>{{ item.value }}{{ item.unit }}</span> |
| | |
| | | |
| | | <!-- 第二行图表 --> |
| | | <el-row :gutter="20" class="charts-section"> |
| | | <!-- 编制达成率 --> |
| | | <el-col :span="12"> |
| | | <el-card class="chart-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>编制达成率</span> |
| | | <el-tag type="warning">各部门对比</el-tag> |
| | | </div> |
| | | </template> |
| | | <div class="chart-container"> |
| | | <div ref="staffingChartRef" class="chart"></div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | |
| | | <!-- 员工流失原因分析 --> |
| | | <el-col :span="12"> |
| | | <el-card class="chart-card"> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, onUnmounted } from 'vue' |
| | | import { ref, onMounted, onUnmounted, nextTick } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import { |
| | | Refresh, |
| | | User, |
| | | TrendCharts, |
| | | DataAnalysis, |
| | | PieChart, |
| | | ArrowUp, |
| | | ArrowDown |
| | | } from '@element-plus/icons-vue' |
| | | import * as echarts from 'echarts' |
| | | import { staffOnJobListPage } from '@/api/personnelManagement/staffOnJob.js' |
| | | import {listDept} from "@/api/system/dept.js"; |
| | | import { |
| | | findStaffAnalysisMonthlyTurnoverRateFor12Months, |
| | | findStaffLeaveReasonAnalysis, |
| | | findStaffAnalysisTotalStatistic |
| | | } from "@/api/personnelManagement/staffAnalytics.js"; |
| | | |
| | | // 响应式数据 |
| | | const loading = ref(false) |
| | |
| | | trend: 0 |
| | | }, |
| | | { |
| | | label: '编制达成率', |
| | | value: 0, |
| | | unit: '%', |
| | | icon: 'DataAnalysis', |
| | | type: 'success', |
| | | trend: 0 |
| | | }, |
| | | { |
| | | label: '在职员工数', |
| | | value: 0, |
| | | unit: '人', |
| | |
| | | |
| | | // 部门数据 |
| | | const departmentData = ref([]) |
| | | // 员工流失原因分析数据 |
| | | const staffLeaveReasons = ref([]) |
| | | // 12个月员工流动流失率分析数据 |
| | | const turnoverRateStatistics = ref([]) |
| | | |
| | | // 获取在职员工数 |
| | | const getStaffCount = async () => { |
| | | // 获取部门数据 |
| | | const getDepartmentData = async () => { |
| | | try { |
| | | const res = await staffOnJobListPage({ staffState: 1, current: 1, size: 1 }) |
| | | const res = await listDept() |
| | | if (res && res.data) { |
| | | keyMetrics.value[3].value = res.data.total || 0 |
| | | departmentData.value = res.data |
| | | } |
| | | } catch (error) { |
| | | console.error('获取在职员工数失败:', error) |
| | | console.error('获取部门数据失败:', error) |
| | | } |
| | | } |
| | | |
| | | const getStaffLeaveReasonAnalysis = async () => { |
| | | try { |
| | | const res = await findStaffLeaveReasonAnalysis() |
| | | if (res && res.data) { |
| | | staffLeaveReasons.value = res.data || [] |
| | | } |
| | | } catch (error) { |
| | | console.error('获取员工流失原因分析失败:', error) |
| | | } |
| | | } |
| | | |
| | | // 修改为返回Promise的异步函数 |
| | | const getMonthlyTurnoverRateFor12Months = async () => { |
| | | try { |
| | | const res = await findStaffAnalysisMonthlyTurnoverRateFor12Months() |
| | | if (res && res.data) { |
| | | turnoverRateStatistics.value = res.data || [] |
| | | } |
| | | } catch (error) { |
| | | console.error('获取12个月员工流动流失率分析数据失败:', error) |
| | | } |
| | | } |
| | | |
| | | const getStaffAnalysisTotalStatistic = async () => { |
| | | try { |
| | | const res = await findStaffAnalysisTotalStatistic() |
| | | if (res && res.data) { |
| | | keyMetrics.value[0].value = res.data.totalFlowRate || 0 |
| | | keyMetrics.value[1].value = res.data.totalTurnoverRate || 0 |
| | | keyMetrics.value[2].value = res.data.currentOnJobCount || 0 |
| | | } |
| | | } catch (error) { |
| | | console.error('获取员工分析总统计数据失败:', error) |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | // 生成模拟数据 |
| | | const generateMockData = () => { |
| | | // 生成关键指标数据 |
| | | keyMetrics.value[0].value = (Math.random() * 5 + 2).toFixed(1) |
| | | keyMetrics.value[0].trend = (Math.random() * 3 - 1.5).toFixed(1) |
| | | |
| | | keyMetrics.value[1].value = (Math.random() * 3 + 1).toFixed(1) |
| | | keyMetrics.value[1].trend = (Math.random() * 2 - 1).toFixed(1) |
| | | |
| | | keyMetrics.value[2].value = (Math.random() * 15 + 85).toFixed(1) |
| | | keyMetrics.value[2].trend = (Math.random() * 3 - 1.5).toFixed(1) |
| | | |
| | | // 生成部门数据 |
| | | const departments = ['技术部', '销售部', '人事部', '财务部', '生产部', '市场部'] |
| | | departmentData.value = departments.map(dept => ({ |
| | | department: dept, |
| | | currentStaff: Math.floor(Math.random() * 30 + 20), |
| | | plannedStaff: Math.floor(Math.random() * 10 + 35), |
| | | staffingRate: Math.floor(Math.random() * 20 + 80), |
| | | turnoverRate: (Math.random() * 4 + 1).toFixed(1), |
| | | attritionRate: (Math.random() * 2 + 0.5).toFixed(1), |
| | | newHires: Math.floor(Math.random() * 5 + 1), |
| | | resignations: Math.floor(Math.random() * 3 + 1), |
| | | status: Math.random() > 0.7 ? '异常' : '正常' |
| | | })) |
| | | } |
| | | |
| | | // 刷新数据 |
| | | // 修改为异步函数,确保数据加载完成后再渲染图表 |
| | | const refreshData = async () => { |
| | | loading.value = true |
| | | try { |
| | | // 模拟API调用延迟 |
| | | await new Promise(resolve => setTimeout(resolve, 500)) |
| | | |
| | | generateMockData() |
| | | loading.value = true |
| | | |
| | | // 等待所有数据加载完成 |
| | | await Promise.all([ |
| | | getDepartmentData(), |
| | | getStaffLeaveReasonAnalysis(), |
| | | getMonthlyTurnoverRateFor12Months(), |
| | | getStaffAnalysisTotalStatistic() |
| | | ]) |
| | | |
| | | await nextTick() |
| | | renderAllCharts() |
| | | |
| | | |
| | | if (!autoRefreshEnabled.value) { |
| | | ElMessage.success('数据刷新成功') |
| | | } |
| | | } catch (error) { |
| | | console.error('刷新数据失败:', error) |
| | | ElMessage.error('刷新数据失败') |
| | | console.error('数据刷新失败:', error) |
| | | ElMessage.error('数据刷新失败') |
| | | } finally { |
| | | loading.value = false |
| | | } |
| | |
| | | if (attritionChartRef.value) { |
| | | attritionChart = echarts.init(attritionChartRef.value) |
| | | } |
| | | |
| | | renderAllCharts() |
| | | |
| | | // 初始化时也先加载数据再渲染图表 |
| | | refreshData() |
| | | }, 300) |
| | | } |
| | | |
| | |
| | | renderAttritionChart() |
| | | } |
| | | |
| | | // 渲染员工流动率趋势图 |
| | | // 修改为使用API返回的实际数据 |
| | | const renderTurnoverChart = () => { |
| | | if (!turnoverChart) return |
| | | |
| | | const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'] |
| | | const turnoverData = months.map(() => (Math.random() * 5 + 2).toFixed(1)) |
| | | const attritionData = months.map(() => (Math.random() * 3 + 1).toFixed(1)) |
| | | |
| | | |
| | | // 使用API返回的实际数据 |
| | | const months = turnoverRateStatistics.value.map(item => item.month) |
| | | const turnoverData = turnoverRateStatistics.value.map(item => item.flowRate || 0) |
| | | const attritionData = turnoverRateStatistics.value.map(item => item.turnoverRate || 0) |
| | | |
| | | const option = { |
| | | title: { |
| | | text: '员工流动率趋势', |
| | |
| | | } |
| | | ] |
| | | } |
| | | |
| | | |
| | | turnoverChart.setOption(option) |
| | | } |
| | | |
| | | // 渲染部门人员分布图 |
| | | const renderDepartmentChart = () => { |
| | | if (!departmentChart) return |
| | | |
| | | |
| | | const data = departmentData.value.map(item => ({ |
| | | name: item.department, |
| | | value: item.currentStaff |
| | | name: item.deptName, |
| | | value: item.staffCount |
| | | })) |
| | | |
| | | |
| | | const option = { |
| | | title: { |
| | | text: '部门人员分布', |
| | |
| | | } |
| | | ] |
| | | } |
| | | |
| | | |
| | | departmentChart.setOption(option) |
| | | } |
| | | |
| | | // 渲染编制达成率图 |
| | | const renderStaffingChart = () => { |
| | | if (!staffingChart) return |
| | | |
| | | const departments = departmentData.value.map(item => item.department) |
| | | |
| | | const departments = departmentData.value.map(item => item.deptName) |
| | | const rates = departmentData.value.map(item => item.staffingRate) |
| | | |
| | | |
| | | const option = { |
| | | title: { |
| | | text: '编制达成率', |
| | |
| | | } |
| | | ] |
| | | } |
| | | |
| | | |
| | | staffingChart.setOption(option) |
| | | } |
| | | |
| | | // 渲染员工流失原因分析图 |
| | | const renderAttritionChart = () => { |
| | | if (!attritionChart) return |
| | | |
| | | const reasons = ['薪资待遇', '职业发展', '工作环境', '个人原因', '其他'] |
| | | const data = reasons.map(() => Math.floor(Math.random() * 20 + 5)) |
| | | |
| | | |
| | | const reasons = staffLeaveReasons.value.map(item => item.reasonText) |
| | | const data = staffLeaveReasons.value.map(item => item.count) |
| | | |
| | | const option = { |
| | | title: { |
| | | text: '员工流失原因分析', |
| | |
| | | } |
| | | ] |
| | | } |
| | | |
| | | |
| | | attritionChart.setOption(option) |
| | | } |
| | | |
| | | |
| | | // 生命周期 |
| | | onMounted(() => { |
| | | generateMockData() |
| | | getStaffCount() |
| | | initCharts() |
| | | startAutoRefresh() |
| | | }) |
| | |
| | | .analytics-container { |
| | | padding: 10px; |
| | | } |
| | | |
| | | |
| | | .page-header { |
| | | padding: 15px; |
| | | } |
| | | |
| | | |
| | | .page-header h2 { |
| | | font-size: 24px; |
| | | } |
| | | |
| | | |
| | | .header-controls { |
| | | flex-direction: column; |
| | | gap: 15px; |
| | | } |
| | | |
| | | |
| | | .refresh-btn { |
| | | margin-left: 0; |
| | | } |
| | | |
| | | |
| | | .metrics-cards .el-col { |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | |
| | | .charts-section .el-col { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | |
| | | .chart-container { |
| | | height: 300px; |
| | | } |
| | |
| | | .page-header h2 { |
| | | font-size: 20px; |
| | | } |
| | | |
| | | |
| | | .card-number { |
| | | font-size: 24px; |
| | | } |
| | | |
| | | |
| | | .chart-container { |
| | | height: 250px; |
| | | } |