feat(报表): 新增请假和报餐统计报表功能

添加报表统计接口和页面,包含以下功能:
1. 请假统计:按类型、部门和月份展示数据
2. 报餐统计:按餐别、部门和日期展示数据
3. 使用echarts实现多种图表展示方式
This commit is contained in:
砂糖
2026-01-17 16:16:07 +08:00
parent 2a501c5486
commit d85beb3359
3 changed files with 458 additions and 0 deletions

View File

@@ -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
})
}

View File

@@ -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
})
}

View 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>