256 lines
8.6 KiB
Vue
256 lines
8.6 KiB
Vue
<template>
|
||
<div class="leave-statistics-container">
|
||
<!-- 1. 时间筛选区域 -->
|
||
<div class="search-form">
|
||
<el-form :inline="true" :model="queryParams" class="demo-form-inline">
|
||
<el-form-item label="请假时间">
|
||
<el-date-picker v-model="dateRange" type="daterange" range-separator="至" start-placeholder="开始时间"
|
||
end-placeholder="结束时间" value-format="yyyy-MM-dd" style="width: 260px"></el-date-picker>
|
||
</el-form-item>
|
||
<el-form-item label="请假人">
|
||
<dict-select v-model="queryParams.applicantName" dict-type="hrm_leave_employee" placeholder="请选择请假人"
|
||
style="width: 260px" />
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
|
||
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
|
||
<el-button icon="el-icon-download" type="warning" plain @click="handleExport">导出</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
</div>
|
||
|
||
<!-- 2. 图表展示区域 - 横向三个图表组件 -->
|
||
<div class="chart-box">
|
||
<div class="chart-item">
|
||
<leave-trend-chart :chart-data="trendChartData" />
|
||
</div>
|
||
<div class="chart-item">
|
||
<leave-dept-chart :chart-data="deptChartData" />
|
||
</div>
|
||
<div class="chart-item">
|
||
<leave-type-chart :chart-data="typeChartData" />
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 3. 请假记录表格区域 - 封装的折叠表格组件 -->
|
||
<div class="table-box">
|
||
<leave-record-table :table-data="tableGroupData" :loading="loading" />
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import { listLeaveRequest } from "@/api/wms/leaveRequest";
|
||
// 引入4个封装的子组件
|
||
import LeaveTrendChart from './components/LeaveTrendChart';
|
||
import LeaveDeptChart from './components/LeaveDeptChart';
|
||
import LeaveTypeChart from './components/LeaveTypeChart';
|
||
import LeaveRecordTable from './components/LeaveRecordTable';
|
||
import DictSelect from '@/components/DictSelect';
|
||
|
||
export default {
|
||
name: 'LeaveStatistics',
|
||
components: {
|
||
LeaveTrendChart,
|
||
LeaveDeptChart,
|
||
LeaveTypeChart,
|
||
LeaveRecordTable,
|
||
DictSelect
|
||
},
|
||
data() {
|
||
return {
|
||
loading: false,
|
||
// 查询参数
|
||
queryParams: {
|
||
pageNum: 1,
|
||
pageSize: 9999, // 查全部数据用于图表统计
|
||
startTime: '',
|
||
endTime: '',
|
||
},
|
||
leaveIds: '', // 请假人ID列表
|
||
dateRange: [], // 时间筛选绑定值
|
||
leaveRequestList: [], // 原始请假数据列表
|
||
// 图表渲染数据
|
||
trendChartData: { xAxis: [], countData: [], dayData: [] }, // 折线图数据
|
||
deptChartData: { xAxis: [], countData: [], dayData: [] }, // 柱状图数据
|
||
typeChartData: { countPie: [], dayPie: [] }, // 饼图数据
|
||
tableGroupData: [] // 表格分组数据(按请假人聚合)
|
||
};
|
||
},
|
||
mounted() {
|
||
// 初始化默认时间:当月第一天 至 当前时间
|
||
this.initDefaultTime();
|
||
// 加载数据
|
||
this.getList();
|
||
},
|
||
methods: {
|
||
// 初始化默认时间 - 当月1号 00:00:00 至 当前时间 yyyy-MM-dd HH:mm:ss
|
||
initDefaultTime() {
|
||
const now = new Date();
|
||
// 当月第一天
|
||
const monthFirst = new Date(now.getFullYear(), now.getMonth(), 1);
|
||
const startTime = this.formatDateTime(monthFirst);
|
||
const endTime = this.formatDateTime(now);
|
||
this.dateRange = [startTime, endTime];
|
||
// this.queryParams.startTime = startTime;
|
||
// this.queryParams.endTime = endTime;
|
||
},
|
||
|
||
// 时间格式化:yyyy-MM-dd HH:mm:ss
|
||
formatDateTime(date) {
|
||
const y = date.getFullYear();
|
||
const m = (date.getMonth() + 1).toString().padStart(2, '0');
|
||
const d = date.getDate().toString().padStart(2, '0');
|
||
const h = date.getHours().toString().padStart(2, '0');
|
||
const mm = date.getMinutes().toString().padStart(2, '0');
|
||
const s = date.getSeconds().toString().padStart(2, '0');
|
||
return `${y}-${m}-${d}`;
|
||
},
|
||
|
||
// 查询按钮事件
|
||
handleQuery() {
|
||
if (this.dateRange && this.dateRange.length === 2) {
|
||
// this.queryParams.startTime = this.dateRange[0];
|
||
// this.queryParams.endTime = this.dateRange[1];
|
||
}
|
||
this.getList();
|
||
},
|
||
|
||
// 重置按钮事件
|
||
resetQuery() {
|
||
this.dateRange = [];
|
||
this.initDefaultTime();
|
||
this.getList();
|
||
},
|
||
|
||
// 调取接口获取数据
|
||
getList() {
|
||
this.loading = true;
|
||
listLeaveRequest(this.queryParams).then(res => {
|
||
this.leaveRequestList = res.rows || [];
|
||
// 提取所有请假人ID
|
||
this.leaveIds = this.leaveRequestList.map(item => item.leaveId).join(',');
|
||
// 核心:数据处理,为图表和表格格式化数据
|
||
this.formatAllData();
|
||
this.loading = false;
|
||
}).catch(() => {
|
||
this.loading = false;
|
||
});
|
||
},
|
||
|
||
handleExport() {
|
||
// 导出逻辑
|
||
if (!this.leaveIds) {
|
||
this.$message.warning('请先查询数据');
|
||
return;
|
||
}
|
||
this.download('/wms/leaveRequest/export', {
|
||
leaveIds: this.leaveIds
|
||
}, '请假记录.xlsx');
|
||
},
|
||
|
||
// 格式化所有图表+表格数据
|
||
formatAllData() {
|
||
const list = this.leaveRequestList;
|
||
// ========== 1. 处理【按时间汇总】折线图数据 ==========
|
||
const timeObj = {};
|
||
list.forEach(item => {
|
||
const day = item.startTime?.split(' ')[0]; // 按日期聚合
|
||
if (!day) return;
|
||
if (!timeObj[day]) {
|
||
timeObj[day] = { count: 0, days: 0 };
|
||
}
|
||
timeObj[day].count += 1; // 记录数+1
|
||
timeObj[day].days += Number(item.leaveDays) || 0; // 请假天数累加
|
||
});
|
||
this.trendChartData.xAxis = Object.keys(timeObj).sort(); // 日期排序
|
||
this.trendChartData.countData = this.trendChartData.xAxis.map(k => timeObj[k].count);
|
||
this.trendChartData.dayData = this.trendChartData.xAxis.map(k => timeObj[k].days);
|
||
|
||
// ========== 2. 处理【按部门汇总】柱状图数据 ==========
|
||
const deptObj = {};
|
||
list.forEach(item => {
|
||
const dept = item.applicantDeptName || '未知部门';
|
||
if (!deptObj[dept]) {
|
||
deptObj[dept] = { count: 0, days: 0 };
|
||
}
|
||
deptObj[dept].count += 1;
|
||
deptObj[dept].days += Number(item.leaveDays) || 0;
|
||
});
|
||
this.deptChartData.xAxis = Object.keys(deptObj);
|
||
this.deptChartData.countData = this.deptChartData.xAxis.map(k => deptObj[k].count);
|
||
this.deptChartData.dayData = this.deptChartData.xAxis.map(k => deptObj[k].days);
|
||
|
||
// ========== 3. 处理【按请假类型汇总】饼图数据 ==========
|
||
const typeObj = {};
|
||
list.forEach(item => {
|
||
const type = item.leaveType || '未知类型';
|
||
if (!typeObj[type]) {
|
||
typeObj[type] = { count: 0, days: 0 };
|
||
}
|
||
typeObj[type].count += 1;
|
||
typeObj[type].days += Number(item.leaveDays) || 0;
|
||
});
|
||
this.typeChartData.countPie = Object.keys(typeObj).map(k => ({ name: k, value: typeObj[k].count }));
|
||
this.typeChartData.dayPie = Object.keys(typeObj).map(k => ({ name: k, value: typeObj[k].days }));
|
||
|
||
console.log(this.tableGroupData, '按请假人分组表格数据');
|
||
// ========== 4. 处理【按请假人分组】表格折叠数据 ==========
|
||
const userObj = {};
|
||
list.forEach(item => {
|
||
const userName = item.applicantName || '未知人员';
|
||
if (!userObj[userName]) {
|
||
userObj[userName] = [];
|
||
}
|
||
userObj[userName].push({
|
||
...item,
|
||
leaveTime: `${item.startTime} ~ ${item.endTime}`, // 拼接请假时间段
|
||
leaveDays: parseInt(item.leaveDays || 0) // 转换为整数
|
||
});
|
||
});
|
||
this.tableGroupData = Object.keys(userObj).map(user => ({
|
||
leaveId: user,
|
||
userName: user,
|
||
leaveCount: userObj[user].length || 0, // 请假次数
|
||
leaveDays: parseInt(userObj[user].reduce((sum, item) => sum + item.leaveDays, 0)), // 请假总时长(整数)
|
||
children: userObj[user] // 折叠子数据
|
||
}));
|
||
console.log(this.tableGroupData, '按请假人分组表格数据');
|
||
}
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.leave-statistics-container {
|
||
padding: 20px;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.search-form {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.chart-box {
|
||
display: flex;
|
||
gap: 15px;
|
||
margin-bottom: 20px;
|
||
height: 320px;
|
||
|
||
.chart-item {
|
||
flex: 1;
|
||
border: 1px solid #ebeef5;
|
||
border-radius: 4px;
|
||
padding: 10px;
|
||
box-sizing: border-box;
|
||
}
|
||
}
|
||
|
||
.table-box {
|
||
width: 100%;
|
||
border: 1px solid #ebeef5;
|
||
border-radius: 4px;
|
||
padding: 10px;
|
||
box-sizing: border-box;
|
||
}
|
||
</style> |