feat(报表): 新增请假和报餐统计报表功能
添加报表统计接口和页面,包含以下功能: 1. 请假统计:按类型、部门和月份展示数据 2. 报餐统计:按餐别、部门和日期展示数据 3. 使用echarts实现多种图表展示方式
This commit is contained in:
@@ -42,3 +42,31 @@ export function delLeaveRequest(leaveId) {
|
||||
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'
|
||||
})
|
||||
}
|
||||
|
||||
// 报表相关接口
|
||||
// 按餐别统计
|
||||
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