<template>
|
<div class="app-container">
|
<div class="search_form">
|
<div>
|
<span class="search_title">发放季度:</span>
|
<el-select
|
style="width: 200px"
|
v-model="searchForm.season"
|
placeholder="请选择"
|
@change="handleQuery"
|
@clear="clearSeason"
|
clearable
|
:disabled="!!searchForm.issueDate"
|
>
|
<el-option
|
v-for="item in jidu"
|
:key="item.value"
|
:label="item.label"
|
:value="item.value"
|
/>
|
</el-select>
|
|
<span class="search_title ml10">发放月份:</span>
|
<el-date-picker
|
style="width: 200px"
|
v-model="searchForm.issueDate"
|
type="month"
|
value-format="YYYY-MM-DD"
|
format="YYYY-MM"
|
placeholder="请选择月份"
|
clearable
|
@change="handleQuery"
|
@clear="clearIssueDaten"
|
:disabled="!!searchForm.season"
|
/>
|
|
<el-button type="primary" style="margin-left: 10px" @click="handleQuery">
|
搜索
|
</el-button>
|
<el-button style="margin-left: 10px" @click="resetHandleQuery">
|
重置
|
</el-button>
|
</div>
|
<div>
|
<el-button @click="handleOut" icon="download">导出</el-button>
|
</div>
|
</div>
|
|
<!-- 发放进度(图表模式) -->
|
<el-row :gutter="20" class="progress-cards">
|
<el-col :span="6">
|
<el-card class="progress-card">
|
<div class="pc-title">发放总数量</div>
|
<div class="pc-value">{{ totalNum }}</div>
|
<div class="pc-sub">已领 + 未领(含超时)</div>
|
</el-card>
|
</el-col>
|
<el-col :span="6">
|
<el-card class="progress-card success">
|
<div class="pc-title">已领取</div>
|
<div class="pc-value">{{ adoptedNum }}</div>
|
<div class="pc-sub">含超时已领取</div>
|
</el-card>
|
</el-col>
|
<el-col :span="6">
|
<el-card class="progress-card warning">
|
<div class="pc-title">未领取</div>
|
<div class="pc-value">{{ unAdoptedNum }}</div>
|
<div class="pc-sub">含超时未领取</div>
|
</el-card>
|
</el-col>
|
<el-col :span="6">
|
<el-card class="progress-card info">
|
<div class="pc-title">领取完成率</div>
|
<div class="pc-value">{{ progressPercentVal }}%</div>
|
<el-progress
|
:percentage="progressPercentVal"
|
:stroke-width="10"
|
status="success"
|
/>
|
</el-card>
|
</el-col>
|
</el-row>
|
|
<el-row :gutter="20" class="charts-section">
|
<el-col :span="12">
|
<el-card class="chart-card" v-loading="statsLoading">
|
<template #header>
|
<div class="card-header">领取进度占比</div>
|
</template>
|
<div ref="pieChartRef" class="chart"></div>
|
</el-card>
|
</el-col>
|
<el-col :span="12">
|
<el-card class="chart-card" v-loading="statsLoading">
|
<template #header>
|
<div class="card-header">进度分布(含超时)</div>
|
</template>
|
<div ref="barChartRef" class="chart"></div>
|
</el-card>
|
</el-col>
|
</el-row>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, reactive, toRefs, onMounted, onUnmounted, computed, nextTick, getCurrentInstance } from 'vue'
|
import dayjs from 'dayjs'
|
import * as echarts from 'echarts'
|
import { progressTotal, progressPercent, progressDistribution } from '@/api/lavorissce/ledger'
|
import { ElMessageBox, ElMessage } from 'element-plus'
|
import { getCurrentMonth } from '@/utils/util'
|
|
const { proxy } = getCurrentInstance()
|
|
// 查询条件
|
const data = reactive({
|
searchForm: {
|
season: getCurrentMonth(),
|
issueDate: '',
|
},
|
})
|
const { searchForm } = toRefs(data)
|
|
// 季度选项
|
const jidu = ref([
|
{ value: '1', label: '第一季度' },
|
{ value: '2', label: '第二季度' },
|
{ value: '3', label: '第三季度' },
|
{ value: '4', label: '第四季度' },
|
])
|
|
// 进度统计(图表数据)
|
const statsLoading = ref(false)
|
// 发放进度-总计(来自 progressTotal 接口)
|
const totalData = ref({
|
totalNum: 0,
|
adoptedNum: 0,
|
unAdoptedNum: 0,
|
progressPercent: 0,
|
})
|
// 领取进度占比(来自 progressPercent 接口,饼图)
|
const percentData = ref([])
|
// 进度分布(来自 progressDistribution 接口,柱状图)
|
const distributionData = ref({
|
ylqNum: 0,
|
wlqNum: 0,
|
csylqNum: 0,
|
cswlqNum: 0,
|
})
|
|
const totalNum = computed(() => Number(totalData.value.totalNum || 0))
|
const adoptedNum = computed(() => Number(totalData.value.adoptedNum || 0))
|
const unAdoptedNum = computed(() => Number(totalData.value.unAdoptedNum || 0))
|
const progressPercentVal = computed(() => Number(totalData.value.progressPercent ?? 0))
|
|
const pieChartRef = ref(null)
|
const barChartRef = ref(null)
|
let pieChart = null
|
let barChart = null
|
|
const resizeCharts = () => {
|
pieChart?.resize()
|
barChart?.resize()
|
}
|
|
const initChartsIfNeeded = async () => {
|
await nextTick()
|
if (pieChartRef.value && !pieChart) pieChart = echarts.init(pieChartRef.value)
|
if (barChartRef.value && !barChart) barChart = echarts.init(barChartRef.value)
|
renderCharts()
|
window.addEventListener('resize', resizeCharts)
|
}
|
|
const renderCharts = () => {
|
const s = distributionData.value
|
const timelyAdopted = Number(s.ylqNum || 0)
|
const timelyUnAdopted = Number(s.wlqNum || 0)
|
const overtimeAdopted = Number(s.csylqNum || 0)
|
const overtimeUnAdopted = Number(s.cswlqNum || 0)
|
|
const colors = ['#67C23A', '#E6A23C']
|
const pieData = percentData.value.length
|
? percentData.value.map((it, i) => ({
|
...it,
|
itemStyle: it.itemStyle || { color: colors[i % colors.length] },
|
}))
|
: [
|
{ name: '已领取', value: adoptedNum.value, itemStyle: { color: '#67C23A' } },
|
{ name: '未领取', value: unAdoptedNum.value, itemStyle: { color: '#E6A23C' } },
|
]
|
|
if (pieChart) {
|
pieChart.setOption(
|
{
|
tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
|
legend: { orient: 'vertical', left: 'left', top: 'middle' },
|
series: [
|
{
|
name: '领取进度',
|
type: 'pie',
|
radius: ['45%', '72%'],
|
center: ['60%', '50%'],
|
label: { formatter: '{b}\n{c}' },
|
data: pieData,
|
emphasis: {
|
itemStyle: {
|
shadowBlur: 10,
|
shadowOffsetX: 0,
|
shadowColor: 'rgba(0, 0, 0, 0.3)',
|
},
|
},
|
},
|
],
|
},
|
true,
|
)
|
}
|
|
if (barChart) {
|
barChart.setOption(
|
{
|
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
|
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
|
xAxis: { type: 'category', data: ['及时', '超时'] },
|
yAxis: { type: 'value' },
|
legend: { data: ['已领取', '未领取'] },
|
series: [
|
{
|
name: '已领取',
|
type: 'bar',
|
stack: 'total',
|
barWidth: 42,
|
itemStyle: { color: '#67C23A' },
|
data: [timelyAdopted, overtimeAdopted],
|
},
|
{
|
name: '未领取',
|
type: 'bar',
|
stack: 'total',
|
itemStyle: { color: '#F56C6C' },
|
data: [timelyUnAdopted, overtimeUnAdopted],
|
},
|
],
|
},
|
true,
|
)
|
}
|
}
|
|
const clearSeason = () => {
|
searchForm.value.season = ''
|
searchForm.value.issueDate = dayjs().format('YYYY-MM-DD')
|
}
|
|
const clearIssueDaten = () => {
|
searchForm.value.issueDate = ''
|
searchForm.value.season = getCurrentMonth()
|
}
|
|
const resetHandleQuery = () => {
|
searchForm.value.issueDate = ''
|
searchForm.value.season = getCurrentMonth()
|
handleQuery()
|
}
|
|
// 查询
|
const handleQuery = async () => {
|
await getStatistics()
|
}
|
|
const getStatistics = async () => {
|
statsLoading.value = true
|
const params = { ...searchForm.value }
|
try {
|
const [totalRes, percentRes, distRes] = await Promise.all([
|
progressTotal(params),
|
progressPercent(params),
|
progressDistribution(params),
|
])
|
|
const d = totalRes?.data || {}
|
totalData.value = {
|
totalNum: d.total ?? 0,
|
adoptedNum: d.adopted ?? 0,
|
unAdoptedNum: d.notAdopted ?? 0,
|
progressPercent: d.adoptedPercent ?? 0,
|
}
|
|
const p = percentRes?.data || {}
|
if (Array.isArray(p)) {
|
percentData.value = p
|
} else if (p.data && Array.isArray(p.data)) {
|
percentData.value = p.data
|
} else if (p.adopted != null || p.notAdopted != null) {
|
percentData.value = [
|
{ name: '已领取', value: Number(p.adopted || 0), itemStyle: { color: '#67C23A' } },
|
{ name: '未领取', value: Number(p.notAdopted || 0), itemStyle: { color: '#E6A23C' } },
|
]
|
} else {
|
percentData.value = []
|
}
|
|
const dist = distRes?.data || {}
|
// 后端返回示例:{ series: [[2,14],[0,0]] }
|
// 约定:series[0] = [及时已领, 及时未领],series[1] = [超时已领, 超时未领]
|
if (
|
Array.isArray(dist.series) &&
|
dist.series.length >= 2 &&
|
Array.isArray(dist.series[0]) &&
|
Array.isArray(dist.series[1]) &&
|
dist.series[0].length >= 2 &&
|
dist.series[1].length >= 2
|
) {
|
distributionData.value = {
|
ylqNum: Number(dist.series[0][0] ?? 0),
|
wlqNum: Number(dist.series[0][1] ?? 0),
|
csylqNum: Number(dist.series[1][0] ?? 0),
|
cswlqNum: Number(dist.series[1][1] ?? 0),
|
}
|
} else {
|
// 兼容其它返回字段命名
|
distributionData.value = {
|
ylqNum: Number(dist.ylqNum ?? dist.timelyAdopted ?? 0),
|
wlqNum: Number(dist.wlqNum ?? dist.timelyUnAdopted ?? 0),
|
csylqNum: Number(dist.csylqNum ?? dist.overtimeAdopted ?? 0),
|
cswlqNum: Number(dist.cswlqNum ?? dist.overtimeUnAdopted ?? 0),
|
}
|
}
|
|
await initChartsIfNeeded()
|
} catch (e) {
|
totalData.value = { totalNum: 0, adoptedNum: 0, unAdoptedNum: 0, progressPercent: 0 }
|
percentData.value = []
|
distributionData.value = { ylqNum: 0, wlqNum: 0, csylqNum: 0, cswlqNum: 0 }
|
renderCharts()
|
} finally {
|
statsLoading.value = false
|
}
|
}
|
|
// 导出
|
const handleOut = () => {
|
ElMessageBox.confirm('选中的内容将被导出,是否确认导出?', '导出', {
|
confirmButtonText: '确认',
|
cancelButtonText: '取消',
|
type: 'warning',
|
})
|
.then(() => {
|
proxy.download(
|
'/lavorIssue/exportCopy',
|
{
|
season: searchForm.value.season,
|
issueDate: searchForm.value.issueDate,
|
},
|
'劳保发放报表.xlsx',
|
)
|
})
|
.catch(() => {
|
ElMessage.info('已取消')
|
})
|
}
|
|
onMounted(() => {
|
handleQuery()
|
})
|
|
onUnmounted(() => {
|
window.removeEventListener('resize', resizeCharts)
|
if (pieChart) {
|
pieChart.dispose()
|
pieChart = null
|
}
|
if (barChart) {
|
barChart.dispose()
|
barChart = null
|
}
|
})
|
</script>
|
|
<style scoped>
|
.progress-cards {
|
margin: 14px 0 18px;
|
}
|
.progress-card :deep(.el-card__body) {
|
padding: 16px;
|
}
|
.pc-title {
|
color: #606266;
|
font-size: 13px;
|
margin-bottom: 8px;
|
}
|
.pc-value {
|
font-size: 28px;
|
font-weight: 700;
|
color: #303133;
|
line-height: 1.2;
|
margin-bottom: 6px;
|
}
|
.pc-sub {
|
color: #909399;
|
font-size: 12px;
|
}
|
|
.charts-section {
|
margin-bottom: 12px;
|
}
|
.chart-card :deep(.el-card__body) {
|
padding: 12px 16px 16px;
|
}
|
.card-header {
|
font-weight: 600;
|
color: #303133;
|
}
|
.chart {
|
height: 320px;
|
width: 100%;
|
}
|
.dynamic-table-container {
|
width: 100%;
|
}
|
|
.pagination-container {
|
margin-top: 20px;
|
display: flex;
|
justify-content: flex-end;
|
}
|
|
::deep(.el-table .el-table__header-wrapper th) {
|
background-color: #f0f1f5 !important;
|
color: #333333;
|
font-weight: 600;
|
}
|
|
::deep(.el-table .el-table__body-wrapper td) {
|
padding: 8px 0;
|
}
|
|
::deep(.el-select) {
|
width: 100%;
|
}
|
|
::deep(.el-input) {
|
width: 100%;
|
}
|
</style>
|