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