| | |
| | | import { ref, onMounted, onUnmounted, nextTick } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import * as echarts from 'echarts' |
| | | import dayjs from 'dayjs' |
| | | import {listDept} from "@/api/system/dept.js"; |
| | | import { |
| | | findStaffAnalysisMonthlyTurnoverRateFor12Months, |
| | | findStaffLeaveReasonAnalysis, |
| | | findStaffAnalysisTotalStatistic |
| | | } from "@/api/personnelManagement/staffAnalytics.js"; |
| | | import { staffOnJobListPage } from "@/api/personnelManagement/staffOnJob.js"; |
| | | import { findStaffLeaveListPage } from "@/api/personnelManagement/staffLeave.js"; |
| | | |
| | | // 响应式数据 |
| | | const loading = ref(false) |
| | |
| | | const staffLeaveReasons = ref([]) |
| | | // 12个月员工流动流失率分析数据 |
| | | const turnoverRateStatistics = ref([]) |
| | | const turnoverRateStatisticsRaw = ref([]) |
| | | const turnoverSeriesNormalized = ref(false) |
| | | |
| | | const staffCounts = ref({ |
| | | onJobTotal: 0, |
| | | leaveTotal: 0, |
| | | totalStaff: 0, |
| | | leaveLast12Months: 0, |
| | | joinLeaveRatio: 1 |
| | | }) |
| | | |
| | | const safeNum = (val) => { |
| | | const num = Number(val) |
| | | return Number.isFinite(num) ? num : 0 |
| | | } |
| | | |
| | | const round2 = (val) => { |
| | | const num = safeNum(val) |
| | | return Number(num.toFixed(2)) |
| | | } |
| | | |
| | | const getListTotal = (res) => safeNum(res?.data?.total ?? res?.data?.count ?? 0) |
| | | |
| | | const getLast12MonthRanges = () => { |
| | | const end = dayjs() |
| | | const start = end.subtract(11, 'month').startOf('month') |
| | | const ranges = [] |
| | | for (let i = 0; i < 12; i++) { |
| | | const cur = start.add(i, 'month') |
| | | ranges.push({ |
| | | month: cur.format('YYYY-MM'), |
| | | start: cur.startOf('month').format('YYYY-MM-DD'), |
| | | end: cur.endOf('month').format('YYYY-MM-DD') |
| | | }) |
| | | } |
| | | return ranges |
| | | } |
| | | |
| | | // 获取部门数据 |
| | | const getDepartmentData = async () => { |
| | |
| | | try { |
| | | const res = await findStaffAnalysisMonthlyTurnoverRateFor12Months() |
| | | if (res && res.data) { |
| | | turnoverRateStatistics.value = res.data || [] |
| | | turnoverRateStatisticsRaw.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 |
| | | const ranges = getLast12MonthRanges() |
| | | const leave12Range = { |
| | | leaveDateStart: ranges[0].start, |
| | | leaveDateEnd: ranges[ranges.length - 1].end |
| | | } |
| | | |
| | | const [totalRes, onJobRes, leaveRes, leave12Res] = await Promise.all([ |
| | | findStaffAnalysisTotalStatistic(), |
| | | staffOnJobListPage({ current: 1, size: 1, staffState: 1 }), |
| | | findStaffLeaveListPage({ current: 1, size: 1 }), |
| | | findStaffLeaveListPage({ current: 1, size: 1, ...leave12Range }) |
| | | ]) |
| | | |
| | | const totalFlowRate = safeNum(totalRes?.data?.totalFlowRate) |
| | | const totalTurnoverRate = safeNum(totalRes?.data?.totalTurnoverRate) |
| | | const joinLeaveRatio = |
| | | totalTurnoverRate > 0 && totalFlowRate > 0 ? totalFlowRate / totalTurnoverRate : 1 |
| | | |
| | | const onJobTotal = getListTotal(onJobRes) |
| | | const leaveTotal = getListTotal(leaveRes) |
| | | const totalStaff = onJobTotal + leaveTotal |
| | | const leaveLast12Months = getListTotal(leave12Res) |
| | | |
| | | staffCounts.value = { |
| | | onJobTotal, |
| | | leaveTotal, |
| | | totalStaff, |
| | | leaveLast12Months, |
| | | joinLeaveRatio: safeNum(joinLeaveRatio) |
| | | } |
| | | |
| | | const turnoverRateNew = |
| | | totalStaff > 0 ? (leaveLast12Months / totalStaff) * 100 : 0 |
| | | const flowRateNew = |
| | | totalStaff > 0 ? (leaveLast12Months * staffCounts.value.joinLeaveRatio / totalStaff) * 100 : 0 |
| | | |
| | | keyMetrics.value[0].value = round2(flowRateNew) |
| | | keyMetrics.value[1].value = round2(turnoverRateNew) |
| | | keyMetrics.value[2].value = onJobTotal || safeNum(totalRes?.data?.currentOnJobCount) |
| | | } catch (error) { |
| | | console.error('获取员工分析总统计数据失败:', error) |
| | | } |
| | | } |
| | | |
| | | const getLeaveCountsByMonthForLast12Months = async () => { |
| | | const ranges = getLast12MonthRanges() |
| | | const tasks = ranges.map((r) => |
| | | findStaffLeaveListPage({ |
| | | current: 1, |
| | | size: 1, |
| | | leaveDateStart: r.start, |
| | | leaveDateEnd: r.end |
| | | }) |
| | | ) |
| | | |
| | | const results = await Promise.allSettled(tasks) |
| | | const counts = {} |
| | | results.forEach((res, idx) => { |
| | | const month = ranges[idx].month |
| | | counts[month] = res.status === 'fulfilled' ? getListTotal(res.value) : 0 |
| | | }) |
| | | const sum = Object.values(counts).reduce((acc, cur) => acc + safeNum(cur), 0) |
| | | return { counts, sum } |
| | | } |
| | | |
| | | const applyNormalizedTurnoverStatistics = async () => { |
| | | const raw = Array.isArray(turnoverRateStatisticsRaw.value) ? turnoverRateStatisticsRaw.value : [] |
| | | if (raw.length === 0) { |
| | | turnoverRateStatistics.value = [] |
| | | turnoverSeriesNormalized.value = false |
| | | return |
| | | } |
| | | |
| | | const totalStaff = safeNum(staffCounts.value.totalStaff) |
| | | const leaveLast12Months = safeNum(staffCounts.value.leaveLast12Months) |
| | | const ratio = safeNum(staffCounts.value.joinLeaveRatio) || 1 |
| | | |
| | | if (totalStaff <= 0 || leaveLast12Months < 0) { |
| | | turnoverRateStatistics.value = raw |
| | | turnoverSeriesNormalized.value = false |
| | | return |
| | | } |
| | | |
| | | try { |
| | | const { counts, sum } = await getLeaveCountsByMonthForLast12Months() |
| | | if (sum > leaveLast12Months) { |
| | | turnoverRateStatistics.value = raw |
| | | turnoverSeriesNormalized.value = false |
| | | return |
| | | } |
| | | |
| | | turnoverRateStatistics.value = raw.map((item) => { |
| | | const monthKey = String(item?.month ?? '') |
| | | const leaveCount = safeNum(counts[monthKey]) |
| | | const turnoverRate = totalStaff > 0 ? (leaveCount / totalStaff) * 100 : 0 |
| | | const flowRate = totalStaff > 0 ? (leaveCount * ratio / totalStaff) * 100 : 0 |
| | | return { |
| | | ...item, |
| | | flowRate: round2(flowRate), |
| | | turnoverRate: round2(turnoverRate) |
| | | } |
| | | }) |
| | | turnoverSeriesNormalized.value = true |
| | | } catch (e) { |
| | | turnoverRateStatistics.value = raw |
| | | turnoverSeriesNormalized.value = false |
| | | } |
| | | } |
| | | |
| | |
| | | getMonthlyTurnoverRateFor12Months(), |
| | | getStaffAnalysisTotalStatistic() |
| | | ]) |
| | | |
| | | await applyNormalizedTurnoverStatistics() |
| | | |
| | | await nextTick() |
| | | renderAllCharts() |
| | |
| | | data: months, |
| | | boundaryGap: false |
| | | }, |
| | | yAxis: { |
| | | yAxis: turnoverSeriesNormalized.value |
| | | ? { |
| | | type: 'value', |
| | | axisLabel: { formatter: '{value}%' }, |
| | | min: 0, |
| | | max: 100 |
| | | } |
| | | : { |
| | | type: 'value', |
| | | axisLabel: { formatter: '{value}%' } |
| | | }, |