feat(报表): 新增请假和报餐统计报表功能
添加报表统计接口和页面,包含以下功能: 1. 请假统计:按类型、部门和月份展示数据 2. 报餐统计:按餐别、部门和日期展示数据 3. 使用echarts实现多种图表展示方式
This commit is contained in:
@@ -42,3 +42,31 @@ export function delLeaveRequest(leaveId) {
|
|||||||
method: 'delete'
|
method: 'delete'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 报表相关接口
|
||||||
|
// 按请假类型统计
|
||||||
|
export function getLeaveTypeCount(query) {
|
||||||
|
return request({
|
||||||
|
url: '/wms/leaveRequest/report/leaveType',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按部门统计
|
||||||
|
export function getDeptCount(query) {
|
||||||
|
return request({
|
||||||
|
url: '/wms/leaveRequest/report/dept',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按月份统计
|
||||||
|
export function getMonthCount(query) {
|
||||||
|
return request({
|
||||||
|
url: '/wms/leaveRequest/report/monthly',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -42,3 +42,31 @@ export function delMealReport(reportId) {
|
|||||||
method: 'delete'
|
method: 'delete'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 报表相关接口
|
||||||
|
// 按餐别统计
|
||||||
|
export function getMealTypeCount(query) {
|
||||||
|
return request({
|
||||||
|
url: '/wms/mealReport/report/mealType',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按部门统计
|
||||||
|
export function getDeptCount(query) {
|
||||||
|
return request({
|
||||||
|
url: '/wms/mealReport/report/dept',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按日期统计
|
||||||
|
export function getDateCount(query) {
|
||||||
|
return request({
|
||||||
|
url: '/wms/mealReport/report/date',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
402
klp-ui/src/views/wms/hrm/report.vue
Normal file
402
klp-ui/src/views/wms/hrm/report.vue
Normal file
@@ -0,0 +1,402 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<!-- 请假申请统计模块 -->
|
||||||
|
<el-card shadow="hover" class="mb15">
|
||||||
|
<div slot="header" class="card-header">
|
||||||
|
<span>📊 请假申请数据统计</span>
|
||||||
|
</div>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<!-- 请假类型统计-饼图+柱状图 占比+数量 -->
|
||||||
|
<el-col :span="12">
|
||||||
|
<div class="chart-title">请假类型分布统计</div>
|
||||||
|
<div id="leaveTypeChart" class="chart-container"></div>
|
||||||
|
</el-col>
|
||||||
|
<!-- 请假部门统计-横向柱状图 -->
|
||||||
|
<el-col :span="12">
|
||||||
|
<div class="chart-title">各部门请假情况统计</div>
|
||||||
|
<div id="leaveDeptChart" class="chart-container"></div>
|
||||||
|
</el-col>
|
||||||
|
<!-- 请假月度趋势统计-组合图 次数+总天数 -->
|
||||||
|
<el-col :span="24">
|
||||||
|
<div class="chart-title">月度请假趋势统计 (次数/总天数)</div>
|
||||||
|
<div id="leaveMonthChart" class="chart-container big-chart"></div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 部门报餐统计模块 -->
|
||||||
|
<el-card shadow="hover">
|
||||||
|
<div slot="header" class="card-header">
|
||||||
|
<span>🍽️ 部门报餐数据统计</span>
|
||||||
|
</div>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<!-- 餐别统计-饼图 堂食/打包占比 -->
|
||||||
|
<el-col :span="8">
|
||||||
|
<div class="chart-title">餐别-堂食/打包占比</div>
|
||||||
|
<div id="mealTypePieChart" class="chart-container"></div>
|
||||||
|
</el-col>
|
||||||
|
<!-- 餐别统计-柱状图 用餐人数+报餐次数 -->
|
||||||
|
<el-col :span="16">
|
||||||
|
<div class="chart-title">各餐别用餐人数&报餐次数</div>
|
||||||
|
<div id="mealTypeBarChart" class="chart-container"></div>
|
||||||
|
</el-col>
|
||||||
|
<!-- 报餐部门统计-柱状图 -->
|
||||||
|
<el-col :span="12">
|
||||||
|
<div class="chart-title">各部门报餐汇总统计</div>
|
||||||
|
<div id="mealDeptChart" class="chart-container"></div>
|
||||||
|
</el-col>
|
||||||
|
<!-- 报餐日期趋势统计 -->
|
||||||
|
<el-col :span="12">
|
||||||
|
<div class="chart-title">日报餐人数趋势统计</div>
|
||||||
|
<div id="mealDateChart" class="chart-container"></div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts'
|
||||||
|
import { getLeaveTypeCount, getDeptCount as getLeaveDeptCount, getMonthCount as getLeaveMonthCount } from '@/api/wms/leaveRequest'
|
||||||
|
import { getMealTypeCount, getDeptCount as getMealDeptCount, getDateCount as getMealDateCount } from '@/api/wms/mealReport'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Report',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// echarts实例对象
|
||||||
|
leaveTypeChart: null,
|
||||||
|
leaveDeptChart: null,
|
||||||
|
leaveMonthChart: null,
|
||||||
|
mealTypePieChart: null,
|
||||||
|
mealTypeBarChart: null,
|
||||||
|
mealDeptChart: null,
|
||||||
|
mealDateChart: null,
|
||||||
|
// 各接口数据存储
|
||||||
|
leaveTypeData: [],
|
||||||
|
leaveDeptData: [],
|
||||||
|
leaveMonthData: [],
|
||||||
|
mealTypeData: [],
|
||||||
|
mealDeptData: [],
|
||||||
|
mealDateData: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// 页面加载完成后初始化所有图表+请求数据
|
||||||
|
this.initAllCharts()
|
||||||
|
this.fetchAllData()
|
||||||
|
// 窗口大小改变时,图表自适应
|
||||||
|
window.addEventListener('resize', this.resizeAllCharts)
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
// 销毁图表实例,防止内存泄漏
|
||||||
|
window.removeEventListener('resize', this.resizeAllCharts)
|
||||||
|
this.disposeAllCharts()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 初始化所有图表容器
|
||||||
|
initAllCharts() {
|
||||||
|
this.leaveTypeChart = echarts.init(document.getElementById('leaveTypeChart'))
|
||||||
|
this.leaveDeptChart = echarts.init(document.getElementById('leaveDeptChart'))
|
||||||
|
this.leaveMonthChart = echarts.init(document.getElementById('leaveMonthChart'))
|
||||||
|
this.mealTypePieChart = echarts.init(document.getElementById('mealTypePieChart'))
|
||||||
|
this.mealTypeBarChart = echarts.init(document.getElementById('mealTypeBarChart'))
|
||||||
|
this.mealDeptChart = echarts.init(document.getElementById('mealDeptChart'))
|
||||||
|
this.mealDateChart = echarts.init(document.getElementById('mealDateChart'))
|
||||||
|
},
|
||||||
|
// 请求所有接口数据
|
||||||
|
fetchAllData() {
|
||||||
|
Promise.all([
|
||||||
|
this.getLeaveTypeCount({}),
|
||||||
|
this.getLeaveDeptCount({}),
|
||||||
|
this.getLeaveMonthCount({}),
|
||||||
|
this.getMealTypeCount({}),
|
||||||
|
this.getMealDeptCount({}),
|
||||||
|
this.getMealDateCount({})
|
||||||
|
]).then(() => {
|
||||||
|
// 所有数据请求完成后绘制图表
|
||||||
|
this.renderLeaveTypeChart()
|
||||||
|
this.renderLeaveDeptChart()
|
||||||
|
this.renderLeaveMonthChart()
|
||||||
|
this.renderMealTypePieChart()
|
||||||
|
this.renderMealTypeBarChart()
|
||||||
|
this.renderMealDeptChart()
|
||||||
|
this.renderMealDateChart()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 图表自适应窗口大小
|
||||||
|
resizeAllCharts() {
|
||||||
|
this.leaveTypeChart?.resize()
|
||||||
|
this.leaveDeptChart?.resize()
|
||||||
|
this.leaveMonthChart?.resize()
|
||||||
|
this.mealTypePieChart?.resize()
|
||||||
|
this.mealTypeBarChart?.resize()
|
||||||
|
this.mealDeptChart?.resize()
|
||||||
|
this.mealDateChart?.resize()
|
||||||
|
},
|
||||||
|
// 销毁所有图表实例
|
||||||
|
disposeAllCharts() {
|
||||||
|
this.leaveTypeChart?.dispose()
|
||||||
|
this.leaveDeptChart?.dispose()
|
||||||
|
this.leaveMonthChart?.dispose()
|
||||||
|
this.mealTypePieChart?.dispose()
|
||||||
|
this.mealTypeBarChart?.dispose()
|
||||||
|
this.mealDeptChart?.dispose()
|
||||||
|
this.mealDateChart?.dispose()
|
||||||
|
},
|
||||||
|
|
||||||
|
// ======================== 请假相关接口及图表绘制 ========================
|
||||||
|
// 请假类型统计
|
||||||
|
getLeaveTypeCount(query) {
|
||||||
|
return getLeaveTypeCount(query).then(res => {
|
||||||
|
this.leaveTypeData = res.data || []
|
||||||
|
return res
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 绘制请假类型饼图
|
||||||
|
renderLeaveTypeChart() {
|
||||||
|
const data = this.leaveTypeData.map(item => ({
|
||||||
|
name: item.type,
|
||||||
|
value: item.count,
|
||||||
|
days: item.total_days
|
||||||
|
}))
|
||||||
|
const option = {
|
||||||
|
tooltip: { trigger: 'item', formatter: '{b}: <br/>请假次数: {c}次 <br/>请假总天数: {d}天' },
|
||||||
|
legend: { orient: 'vertical', top: 'middle', right: 10 },
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '请假类型',
|
||||||
|
type: 'pie',
|
||||||
|
radius: ['40%', '70%'],
|
||||||
|
center: ['40%', '50%'],
|
||||||
|
avoidLabelOverlap: false,
|
||||||
|
itemStyle: { borderRadius: 8, borderColor: '#fff', borderWidth: 2 },
|
||||||
|
color: ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399'],
|
||||||
|
label: { show: false, position: 'center' },
|
||||||
|
emphasis: { label: { show: true, fontSize: 16, fontWeight: 'bold' } },
|
||||||
|
labelLine: { show: false },
|
||||||
|
data: data
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
this.leaveTypeChart.setOption(option)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 部门请假统计
|
||||||
|
getLeaveDeptCount(query) {
|
||||||
|
return getLeaveDeptCount(query).then(res => {
|
||||||
|
this.leaveDeptData = res.data || []
|
||||||
|
return res
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 绘制部门请假横向柱状图
|
||||||
|
renderLeaveDeptChart() {
|
||||||
|
const xData = this.leaveDeptData.map(item => item.dept_name)
|
||||||
|
const countData = this.leaveDeptData.map(item => item.count)
|
||||||
|
const dayData = this.leaveDeptData.map(item => Number(item.total_days))
|
||||||
|
const option = {
|
||||||
|
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
|
||||||
|
legend: { top: 0, right: 0 },
|
||||||
|
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
|
||||||
|
xAxis: { type: 'value', axisLabel: { formatter: '{value}' } },
|
||||||
|
yAxis: { type: 'category', data: xData },
|
||||||
|
series: [
|
||||||
|
{ name: '请假次数', type: 'bar', barWidth: '40%', data: countData, itemStyle: { color: '#409EFF' } },
|
||||||
|
{ name: '请假天数', type: 'bar', barWidth: '40%', data: dayData, itemStyle: { color: '#F56C6C' } }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
this.leaveDeptChart.setOption(option)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 月份请假统计
|
||||||
|
getLeaveMonthCount(query) {
|
||||||
|
return getLeaveMonthCount(query).then(res => {
|
||||||
|
this.leaveMonthData = res.data || []
|
||||||
|
return res
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 绘制月度请假趋势组合图
|
||||||
|
renderLeaveMonthChart() {
|
||||||
|
const xData = this.leaveMonthData.map(item => item.month)
|
||||||
|
const countData = this.leaveMonthData.map(item => item.count)
|
||||||
|
const dayData = this.leaveMonthData.map(item => Number(item.total_days))
|
||||||
|
const option = {
|
||||||
|
tooltip: { trigger: 'axis' },
|
||||||
|
legend: { top: 0, right: 0 },
|
||||||
|
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
|
||||||
|
xAxis: { type: 'category', boundaryGap: false, data: xData },
|
||||||
|
yAxis: [{ type: 'value', name: '请假次数' }, { type: 'value', name: '请假天数', right: '15%' }],
|
||||||
|
series: [
|
||||||
|
{ name: '请假次数', type: 'bar', yAxisIndex: 0, data: countData, itemStyle: { color: '#409EFF' } },
|
||||||
|
{ name: '请假总天数', type: 'line', yAxisIndex: 1, data: dayData, itemStyle: { color: '#F56C6C' }, lineStyle: { width: 3 }, symbol: 'circle' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
this.leaveMonthChart.setOption(option)
|
||||||
|
},
|
||||||
|
|
||||||
|
// ======================== 报餐相关接口及图表绘制 ========================
|
||||||
|
// 餐别统计
|
||||||
|
getMealTypeCount(query) {
|
||||||
|
return getMealTypeCount(query).then(res => {
|
||||||
|
this.mealTypeData = res.data || []
|
||||||
|
return res
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 绘制餐别-堂食打包占比饼图
|
||||||
|
renderMealTypePieChart() {
|
||||||
|
let totalDineIn = 0
|
||||||
|
let totalTakeout = 0
|
||||||
|
this.mealTypeData.forEach(item => {
|
||||||
|
totalDineIn += Number(item.total_dine_in)
|
||||||
|
totalTakeout += Number(item.total_takeout)
|
||||||
|
})
|
||||||
|
const option = {
|
||||||
|
tooltip: { trigger: 'item', formatter: '{b}: {c}人 ({d}%)' },
|
||||||
|
legend: { orient: 'vertical', bottom: 0, left: 'center' },
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '用餐方式',
|
||||||
|
type: 'pie',
|
||||||
|
radius: ['30%', '70%'],
|
||||||
|
center: ['50%', '40%'],
|
||||||
|
itemStyle: { borderRadius: 8, borderColor: '#fff', borderWidth: 2 },
|
||||||
|
color: ['#67C23A', '#E6A23C'],
|
||||||
|
data: [
|
||||||
|
{ name: '堂食人数', value: totalDineIn },
|
||||||
|
{ name: '打包人数', value: totalTakeout }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
this.mealTypePieChart.setOption(option)
|
||||||
|
},
|
||||||
|
// 绘制餐别人数+次数柱状图
|
||||||
|
renderMealTypeBarChart() {
|
||||||
|
const xData = this.mealTypeData.map(item => item.meal_name)
|
||||||
|
const peopleData = this.mealTypeData.map(item => Number(item.total_people))
|
||||||
|
const countData = this.mealTypeData.map(item => item.report_count)
|
||||||
|
const option = {
|
||||||
|
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
|
||||||
|
legend: { top: 0, right: 0 },
|
||||||
|
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
|
||||||
|
xAxis: { type: 'category', data: xData },
|
||||||
|
yAxis: { type: 'value' },
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '总用餐人数',
|
||||||
|
type: 'bar',
|
||||||
|
data: peopleData,
|
||||||
|
itemStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ offset: 0, color: '#409EFF' }, { offset: 1, color: '#66B1FF' }]) }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '报餐次数',
|
||||||
|
type: 'line',
|
||||||
|
data: countData,
|
||||||
|
itemStyle: { color: '#F56C6C' },
|
||||||
|
lineStyle: { width: 3 },
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 8
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
this.mealTypeBarChart.setOption(option)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 报餐部门统计
|
||||||
|
getMealDeptCount(query) {
|
||||||
|
return getMealDeptCount(query).then(res => {
|
||||||
|
this.mealDeptData = res.data || []
|
||||||
|
return res
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 绘制部门报餐柱状图
|
||||||
|
renderMealDeptChart() {
|
||||||
|
const xData = this.mealDeptData.map(item => item.dept_name)
|
||||||
|
const peopleData = this.mealDeptData.map(item => Number(item.total_people))
|
||||||
|
const dineInData = this.mealDeptData.map(item => Number(item.total_dine_in))
|
||||||
|
const takeoutData = this.mealDeptData.map(item => Number(item.total_takeout))
|
||||||
|
const option = {
|
||||||
|
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
|
||||||
|
legend: { top: 0, right: 0 },
|
||||||
|
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
|
||||||
|
xAxis: { type: 'category', data: xData },
|
||||||
|
yAxis: { type: 'value', name: '人数' },
|
||||||
|
series: [
|
||||||
|
{ name: '总人数', type: 'bar', data: peopleData, itemStyle: { color: '#409EFF' } },
|
||||||
|
{ name: '堂食', type: 'bar', data: dineInData, itemStyle: { color: '#67C23A' } },
|
||||||
|
{ name: '打包', type: 'bar', data: takeoutData, itemStyle: { color: '#E6A23C' } }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
this.mealDeptChart.setOption(option)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 报餐日期统计
|
||||||
|
getMealDateCount(query) {
|
||||||
|
return getMealDateCount(query).then(res => {
|
||||||
|
this.mealDateData = res.data || []
|
||||||
|
return res
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 绘制日报餐趋势图
|
||||||
|
renderMealDateChart() {
|
||||||
|
const xData = this.mealDateData.map(item => item.report_date)
|
||||||
|
const peopleData = this.mealDateData.map(item => Number(item.total_people))
|
||||||
|
const dineInData = this.mealDateData.map(item => Number(item.total_dine_in))
|
||||||
|
const takeoutData = this.mealDateData.map(item => Number(item.total_takeout))
|
||||||
|
const option = {
|
||||||
|
tooltip: { trigger: 'axis' },
|
||||||
|
legend: { top: 0, right: 0 },
|
||||||
|
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
|
||||||
|
xAxis: { type: 'category', boundaryGap: false, data: xData },
|
||||||
|
yAxis: { type: 'value', name: '人数' },
|
||||||
|
series: [
|
||||||
|
{ name: '总用餐人数', type: 'line', data: peopleData, itemStyle: { color: '#409EFF' }, lineStyle: { width: 3 } },
|
||||||
|
{ name: '堂食人数', type: 'line', data: dineInData, itemStyle: { color: '#67C23A' }, lineStyle: { width: 2 } },
|
||||||
|
{ name: '打包人数', type: 'line', data: takeoutData, itemStyle: { color: '#E6A23C' }, lineStyle: { width: 2 } }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
this.mealDateChart.setOption(option)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 页面整体样式 */
|
||||||
|
.app-container {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.page-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
/* 卡片样式 */
|
||||||
|
.mb15 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
.card-header {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
/* 图表标题 */
|
||||||
|
.chart-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
/* 图表容器基础样式 */
|
||||||
|
.chart-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 300px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
/* 大图表样式 */
|
||||||
|
.big-chart {
|
||||||
|
height: 400px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user