From 3038089ced9bd8943ba1eeb1c12fd66643db201e Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期一, 02 三月 2026 10:36:26 +0800
Subject: [PATCH] 金鹰黄金 1.劳保管理添加发放进度报表页面
---
src/api/lavorissce/ledger.js | 27 +++
src/views/lavorissue/report/index.vue | 447 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 474 insertions(+), 0 deletions(-)
diff --git a/src/api/lavorissce/ledger.js b/src/api/lavorissce/ledger.js
index f4f710c..2ee516e 100644
--- a/src/api/lavorissce/ledger.js
+++ b/src/api/lavorissce/ledger.js
@@ -18,6 +18,33 @@
})
}
+// 鍙戞斁杩涘害-鎬昏
+export function progressTotal(params) {
+ return request({
+ url: '/lavorIssue/progressTotal',
+ method: 'get',
+ params
+ })
+}
+
+// 棰嗗彇杩涘害鍗犳瘮
+export function progressPercent(params) {
+ return request({
+ url: '/lavorIssue/progressPercent',
+ method: 'get',
+ params
+ })
+}
+
+// 杩涘害鍒嗗竷
+export function progressDistribution(params) {
+ return request({
+ url: '/lavorIssue/progressDistribution',
+ method: 'get',
+ params
+ })
+}
+
export function statisticsList(params) {
return request({
url: '/lavorIssue/statisticsList',
diff --git a/src/views/lavorissue/report/index.vue b/src/views/lavorissue/report/index.vue
new file mode 100644
index 0000000..40ad8fb
--- /dev/null
+++ b/src/views/lavorissue/report/index.vue
@@ -0,0 +1,447 @@
+<template>
+ <div class="app-container">
+ <div class="search_form">
+ <div>
+ <span class="search_title">鍙戞斁瀛e害锛�</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)
+
+// 瀛e害閫夐」
+const jidu = ref([
+ { value: '1', label: '绗竴瀛e害' },
+ { value: '2', label: '绗簩瀛e害' },
+ { value: '3', label: '绗笁瀛e害' },
+ { value: '4', label: '绗洓瀛e害' },
+])
+
+// 杩涘害缁熻锛堝浘琛ㄦ暟鎹級
+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]] }
+ // 绾﹀畾锛歴eries[0] = [鍙婃椂宸查, 鍙婃椂鏈]锛宻eries[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>
+
--
Gitblit v1.9.3