feat: 考勤分析增加薪资支出

This commit is contained in:
砂糖
2025-09-18 10:34:11 +08:00
parent 8d9d7bac33
commit 5c6e2bfb3c

View File

@@ -25,7 +25,7 @@
</div> --> </div> -->
<!-- 筛选器 --> <!-- 筛选器 -->
<div class="filters flex items-center gap-4"> <div class="filters flex items-center gap-4">
<el-dropdown trigger="hover"> <!-- <el-dropdown trigger="hover">
<el-button class="filter-btn whitespace-nowrap"> <el-button class="filter-btn whitespace-nowrap">
记录类型<el-icon class="el-icon--right"> 记录类型<el-icon class="el-icon--right">
<ArrowDown /> <ArrowDown />
@@ -40,7 +40,7 @@
</el-dropdown-item> </el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown> -->
<!-- <el-dropdown trigger="click"> <!-- <el-dropdown trigger="click">
<el-button class="filter-btn whitespace-nowrap"> <el-button class="filter-btn whitespace-nowrap">
状态筛选<el-icon class="el-icon--right"> 状态筛选<el-icon class="el-icon--right">
@@ -123,6 +123,23 @@
<div id="heatmapChart" class="chart-container"></div> <div id="heatmapChart" class="chart-container"></div>
</el-card> </el-card>
</el-col> </el-col>
<el-col :span="12" class="mt-6">
<el-card shadow="hover" class="chart-card">
<template #header>
<h3 class="chart-title text-lg font-medium">薪资支出趋势</h3>
</template>
<div id="salaryTrendChart" class="chart-container"></div>
</el-card>
</el-col>
<el-col :span="12" class="mt-6">
<el-card shadow="hover" class="chart-card">
<template #header>
<h3 class="chart-title text-lg font-medium">薪资支出排行按人</h3>
</template>
<div id="salaryRankChart" class="chart-container"></div>
</el-card>
</el-col>
</el-row> </el-row>
</div> </div>
<!-- 数据表格 --> <!-- 数据表格 -->
@@ -130,7 +147,6 @@
<el-card shadow="hover" class="table-card"> <el-card shadow="hover" class="table-card">
<el-table :data="tableList" class="full-width-table"> <el-table :data="tableList" class="full-width-table">
<el-table-column v-for="col in tableColumns" :key="col.key" :prop="col.key" :label="col.label"> <el-table-column v-for="col in tableColumns" :key="col.key" :prop="col.key" :label="col.label">
<template #default="{ row }" v-if="col.key === 'recordType'"> <template #default="{ row }" v-if="col.key === 'recordType'">
<div class="flex items-center"> <div class="flex items-center">
<el-icon :class="getTypeIconColor(row.recordType)"> <el-icon :class="getTypeIconColor(row.recordType)">
@@ -248,13 +264,21 @@ const statistics = ref([
trend: 8.3, trend: 8.3,
icon: 'Document', icon: 'Document',
iconColor: 'text-green-500' iconColor: 'text-green-500'
},
{
label: '薪资总支出',
value: '100,000',
trend: 12.5,
icon: 'Money',
iconColor: 'text-blue-500'
} }
]); ]);
const setStatistics = ({ totalDuration, totalRecords, averageDuration }) => { const setStatistics = ({ totalDuration, totalRecords, averageDuration, totalWage }) => {
statistics.value[0].value = totalRecords statistics.value[0].value = totalRecords
statistics.value[1].value = averageDuration statistics.value[1].value = averageDuration
statistics.value[2].value = totalDuration statistics.value[2].value = totalDuration
statistics.value[3].value = totalWage
} }
const setTrend = ({ attendanceData, overtimeData, travelData, xAxis }) => { const setTrend = ({ attendanceData, overtimeData, travelData, xAxis }) => {
@@ -444,6 +468,85 @@ const setDuration = ({ durationData, xAxis }) => {
}); });
} }
// 新增1. 薪资支出趋势折线图配置
const setSalaryTrend = ({ xAxis, salaryData }) => {
salaryTrendChart.value.setOption({
animation: false,
tooltip: {
trigger: 'axis',
formatter: '{b}<br/>薪资支出:¥{c}', // 显示日期+薪资(带¥符号)
axisPointer: { type: 'shadow' }
},
legend: { data: ['薪资支出'], left: 'center', top: 0 },
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
xAxis: {
type: 'category',
data: xAxis,
axisLabel: { rotate: 30, margin: 10 } // 日期旋转避免重叠
},
yAxis: {
type: 'value',
name: '薪资(元)',
min: 0,
axisLabel: { formatter: '¥{value}' } // y轴显示¥符号
},
series: [
{
name: '薪资支出',
type: 'line',
data: salaryData,
lineStyle: { color: '#e11d48', width: 2 }, // 红色系(区分其他图表)
symbol: 'circle',
symbolSize: 6,
itemStyle: { color: '#e11d48' }
}
]
});
};
// 新增2. 薪资支出按人柱状图配置(横向,参考出勤排行)
const setSalaryRank = ({ users, salaries }) => {
salaryRankChart.value.setOption({
animation: false,
tooltip: {
trigger: 'axis',
formatter: '{b}<br/>总薪资:¥{c}',
axisPointer: { type: 'shadow' }
},
legend: { data: ['总薪资'], left: 'left', top: 0 },
grid: { left: '15%', right: '8%', bottom: '3%', containLabel: true }, // 左移适配用户名
xAxis: {
type: 'value',
name: '薪资(元)',
min: 0,
axisLabel: { formatter: '¥{value}' }
},
yAxis: {
type: 'category',
data: users,
axisLabel: { rotate: 0, margin: 15 }
},
series: [
{
name: '总薪资',
type: 'bar',
data: salaries,
itemStyle: {
color: '#e11d48',
borderRadius: [0, 4, 4, 0] // 横向柱状图右侧圆角
},
label: {
show: true,
position: 'right',
formatter: '¥{c}', // 标签显示¥符号
fontSize: 12
}
}
]
});
};
const getTypeIcon = (type) => { const getTypeIcon = (type) => {
const icons = { const icons = {
attendance: Check, attendance: Check,
@@ -461,6 +564,8 @@ const trendChart = ref(null);
const durationChart = ref(null); const durationChart = ref(null);
const heatmapChart = ref(null); const heatmapChart = ref(null);
const attendanceRankChart = ref(null); const attendanceRankChart = ref(null);
const salaryTrendChart = ref(null); // 薪资支出趋势折线图
const salaryRankChart = ref(null); // 薪资支出按人柱状图
const initCharts = () => { const initCharts = () => {
trendChart.value = echarts.init(document.querySelector('#trendChart')); trendChart.value = echarts.init(document.querySelector('#trendChart'));
@@ -468,6 +573,8 @@ const initCharts = () => {
attendanceRankChart.value = echarts.init(document.querySelector('#attendanceRankChart')); attendanceRankChart.value = echarts.init(document.querySelector('#attendanceRankChart'));
durationChart.value = echarts.init(document.querySelector('#durationChart')); durationChart.value = echarts.init(document.querySelector('#durationChart'));
heatmapChart.value = echarts.init(document.querySelector('#heatmapChart')); heatmapChart.value = echarts.init(document.querySelector('#heatmapChart'));
salaryTrendChart.value = echarts.init(document.querySelector('#salaryTrendChart'));
salaryRankChart.value = echarts.init(document.querySelector('#salaryRankChart'));
}; };
const updateCharts = () => { const updateCharts = () => {
@@ -480,6 +587,11 @@ const updateCharts = () => {
setAttendanceRank(attendanceRankData) setAttendanceRank(attendanceRankData)
const durationData = formatters.duration(list.value) const durationData = formatters.duration(list.value)
setDuration(durationData) setDuration(durationData)
// 新增:更新薪资图表
const salaryTrendData = formatters.salaryTrend(list.value);
setSalaryTrend(salaryTrendData);
const salaryRankData = formatters.salaryRank(list.value);
setSalaryRank(salaryRankData);
} }
const formatters = { const formatters = {
@@ -489,11 +601,13 @@ const formatters = {
const totalRecords = list.length const totalRecords = list.length
// 可能出现NAN所以需要处理 // 可能出现NAN所以需要处理
const averageDuration = totalRecords > 0 ? (totalDuration / totalRecords).toFixed(2) : 0 const averageDuration = totalRecords > 0 ? (totalDuration / totalRecords).toFixed(2) : 0
const totalWage = list.reduce((acc, item) => acc + item.wage, 0)
console.log(totalDuration, totalRecords, averageDuration) console.log(totalDuration, totalRecords, averageDuration)
return { return {
totalDuration, totalDuration,
totalRecords, totalRecords,
averageDuration averageDuration,
totalWage
} }
}, },
trend: (list) => { trend: (list) => {
@@ -693,6 +807,55 @@ const formatters = {
xAxis: Object.keys(map), xAxis: Object.keys(map),
durationData: Object.values(map) durationData: Object.values(map)
} }
},
// 新增1. 薪资支出趋势数据格式化(按日期分组)
salaryTrend: (list) => {
const salaryData = [];
const dateMap = new Map(); // 去重日期并排序
// 按日期累加薪资
list.forEach(item => {
const { recordDate, wage = 0 } = item; // 兼容无wage字段的情况
if (!dateMap.has(recordDate)) dateMap.set(recordDate, true);
const exists = salaryData.find(i => i.date === recordDate);
if (exists) {
exists.salary += parseFloat(wage);
} else {
salaryData.push({ date: recordDate, salary: parseFloat(wage) });
}
});
// 日期排序,确保折线图顺序正确
const xAxis = Array.from(dateMap.keys()).sort();
// 补全缺失日期的薪资为0
const filledSalaryData = xAxis.map(date => {
const item = salaryData.find(i => i.date === date);
return item ? item.salary.toFixed(2) : 0;
});
return { xAxis, salaryData: filledSalaryData };
},
// 新增2. 薪资支出按人排行数据格式化取前10
salaryRank: (list) => {
const userSalary = {};
// 按用户名累加总薪资
list.forEach(item => {
const { nickName, wage = 0 } = item;
userSalary[nickName] = (userSalary[nickName] || 0) + parseFloat(wage);
});
// 按薪资降序排序取前10名
const sortedUsers = Object.entries(userSalary)
.sort((a, b) => b[1] - a[1])
.slice(0, 10);
return {
users: sortedUsers.map(item => item[0]), // 用户名
salaries: sortedUsers.map(item => item[1].toFixed(2)) // 总薪资保留2位小数
};
} }
} }