From 925b485e661682a2fcfc8b8a47a8148d16ec818e Mon Sep 17 00:00:00 2001
From: riku <risaku@163.com>
Date: 星期六, 11 十月 2025 13:08:54 +0800
Subject: [PATCH] 新增最终数据产品月度巡查简报

---
 src/views/fysp/data-product/middle-data-product/ProdProblemCountSummaryProxy.js |   83 +++++
 src/views/fysp/data-product/components/ProdQueryOptCompare.vue                  |    6 
 src/utils/echart-util.js                                                        |   42 ++
 public/扬尘污染监管简报模板.docx                                                          |    0 
 src/views/fysp/data-product/middle-data-product/ProdProblemCountSummary.vue     |   96 +++---
 src/views/fysp/data-product/components/BaseProdProcess.vue                      |    2 
 src/views/fysp/data-product/components/ProdQueryOpt.vue                         |    5 
 src/views/fysp/data-product/final-data-product/ProdMonInspecReport.vue          |  501 +++++++++++++++++++++++++++++++++++
 src/views/fysp/data-product/components/ProdDownload.vue                         |   41 ++
 src/views/fysp/support/JingAnNightConstruction.vue                              |   25 +
 src/constants/menu.js                                                           |    7 
 src/router/index.js                                                             |   15 +
 12 files changed, 758 insertions(+), 65 deletions(-)

diff --git "a/public/\346\211\254\345\260\230\346\261\241\346\237\223\347\233\221\347\256\241\347\256\200\346\212\245\346\250\241\346\235\277.docx" "b/public/\346\211\254\345\260\230\346\261\241\346\237\223\347\233\221\347\256\241\347\256\200\346\212\245\346\250\241\346\235\277.docx"
new file mode 100644
index 0000000..9476e07
--- /dev/null
+++ "b/public/\346\211\254\345\260\230\346\261\241\346\237\223\347\233\221\347\256\241\347\256\200\346\212\245\346\250\241\346\235\277.docx"
Binary files differ
diff --git a/src/constants/menu.js b/src/constants/menu.js
index 2ecd4b8..b2d9a36 100644
--- a/src/constants/menu.js
+++ b/src/constants/menu.js
@@ -112,7 +112,12 @@
         path: '/fysp/data-product/scenereport',
         icon: 'Document',
         name: '鍦烘櫙鍒嗘瀽鎶ュ憡'
-      }
+      },
+      {
+        path: '/fysp/data-product/final/monInspecReport',
+        icon: 'Document',
+        name: '宸℃煡鏈堝害绠�鎶�'
+      },
     ]
   },
   // {
diff --git a/src/router/index.js b/src/router/index.js
index ba8f36f..1fed002 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -208,6 +208,21 @@
                     ]
                   },
                 ]
+              },
+              {
+                name: 'dataProdFinal',
+                path: 'final',
+                children: [
+                  {
+                    // 鏈�缁堜骇鍝�-宸℃煡鏈堝害绠�鎶�
+                    name: 'MonInspecReport',
+                    path: 'monInspecReport',
+                    component: () =>
+                      import(
+                        '@/views/fysp/data-product/final-data-product/ProdMonInspecReport.vue'
+                      ),
+                  },
+                ]
               }
             ]
           },
diff --git a/src/utils/echart-util.js b/src/utils/echart-util.js
index 7f907dd..85d2841 100644
--- a/src/utils/echart-util.js
+++ b/src/utils/echart-util.js
@@ -1,3 +1,5 @@
+import * as echarts from 'echarts';
+
 function pieChartOption() {
   return {
     color: [
@@ -97,7 +99,7 @@
     legend: {
       show: true,
       orient: 'horizontal',
-      bottom: '0%', // 鍥句緥搴曢儴姘村钩鎺掑垪
+      bottom: '0%' // 鍥句緥搴曢儴姘村钩鎺掑垪
     },
     grid: {
       // left: '3%',
@@ -114,7 +116,7 @@
         alignWithLabel: true
       },
       axisLabel: {
-        rotate: 45,
+        rotate: 45
       }
     },
     yAxis: {
@@ -165,4 +167,38 @@
   document.body.removeChild(link);
 }
 
-export { pieChartOption, barChartOption, downloadChartImage };
+function getChartUrl(option) {
+  // 1. 鍒涘缓涓存椂DOM鍏冪礌
+  const div = document.createElement('div');
+  // div.style.width = '330px';
+  // div.style.height = '160px';
+  div.style.width = '800px';
+  div.style.height = '400px';
+  document.body.appendChild(div);
+
+  // 2. 鍒濆鍖杄charts瀹炰緥
+  const myChart = echarts.init(div);
+
+  // 4. 璁剧疆鍥捐〃閰嶇疆椤�
+  myChart.setOption(option);
+
+  return new Promise((resolve, reject) => {
+    // 寤惰繜鎵ц纭繚缁樺埗瀹屾垚
+    setTimeout(() => {
+      // 5. 灏嗗浘琛ㄨ浆鎹负base64鍥剧墖
+      const imageBase64 = myChart.getDataURL({
+        type: 'png',
+        pixelRatio: 2, // 鎻愰珮鍥剧墖娓呮櫚搴�
+        backgroundColor: '#fff',
+        excludeComponents: ['toolbox']
+      });
+
+      // 6. 閿�姣佸疄渚嬪苟绉婚櫎涓存椂DOM
+      myChart.dispose();
+      document.body.removeChild(div);
+      resolve(imageBase64);
+    }, 1000);
+  });
+}
+
+export { pieChartOption, barChartOption, downloadChartImage, getChartUrl };
diff --git a/src/views/fysp/data-product/components/BaseProdProcess.vue b/src/views/fysp/data-product/components/BaseProdProcess.vue
index cc6d0de..66b5d4e 100644
--- a/src/views/fysp/data-product/components/BaseProdProcess.vue
+++ b/src/views/fysp/data-product/components/BaseProdProcess.vue
@@ -74,7 +74,7 @@
       >
         <div v-show="showStep3Content">
           <template v-if="$slots.step3">
-            <slot name="step3"></slot>
+            <slot name="step3" :onDownload="onDownload" :queryOpt="queryOpt"></slot>
           </template>
           <template v-else>
             <ProdDownload
diff --git a/src/views/fysp/data-product/components/ProdDownload.vue b/src/views/fysp/data-product/components/ProdDownload.vue
index 8c073f1..c7552a1 100644
--- a/src/views/fysp/data-product/components/ProdDownload.vue
+++ b/src/views/fysp/data-product/components/ProdDownload.vue
@@ -15,8 +15,14 @@
       </el-form-item>
       <el-form-item label="浜у搧褰㈠紡">
         <el-radio-group v-model="downloadType">
-          <el-radio value="1"> Excel琛ㄥ崟 </el-radio>
-          <el-radio value="2" :disabled="true"> Word鏂囨。 </el-radio>
+          <el-radio
+            v-for="item in _downloadTypeOptions"
+            :key="item.value"
+            :value="item.value"
+            :disabled="item.disabled"
+          >
+            {{ item.label }}
+          </el-radio>
         </el-radio-group>
       </el-form-item>
     </el-form>
@@ -48,11 +54,33 @@
   loading: {
     type: Boolean,
     default: false
+  },
+  downloadTypeOptions: {
+    type: Array,
+    default: () => [
+      {
+        value: '1',
+        label: 'Excel琛ㄥ崟'
+      },
+      {
+        value: '2',
+        label: 'Word鏂囨。'
+      }
+    ]
+  },
+  // 涓嬭浇绫诲瀷鏄惁鏈夋晥
+  downloadTypeValid: {
+    type: Array,
+    default: () => ['1']
+  },
+  defaultDownloadType: {
+    type: String,
+    default: '1'
   }
 });
 const emit = defineEmits(['submit']);
 
-const downloadType = ref('1');
+const downloadType = ref(props.defaultDownloadType);
 const opts = computed(() => {
   if (props.queryOpt instanceof Array && props.queryOpt.length > 0) {
     return props.queryOpt[0];
@@ -61,6 +89,13 @@
   }
 });
 
+const _downloadTypeOptions = computed(() => {
+  return props.downloadTypeOptions.map((item) => ({
+    ...item,
+    disabled: !props.downloadTypeValid.includes(item.value)
+  }));
+});
+
 const submit = () => {
   emit('submit', {
     downloadType: downloadType.value
diff --git a/src/views/fysp/data-product/components/ProdQueryOpt.vue b/src/views/fysp/data-product/components/ProdQueryOpt.vue
index a1ac625..3c4cbc4 100644
--- a/src/views/fysp/data-product/components/ProdQueryOpt.vue
+++ b/src/views/fysp/data-product/components/ProdQueryOpt.vue
@@ -33,7 +33,7 @@
     type: Boolean,
     default: false
   },
-  active:{
+  active: {
     type: Boolean,
     default: true
   }
@@ -60,8 +60,7 @@
     townName: options.topTask.townname,
     startTime: dayjs(options.topTask.starttime).format('YYYY-MM-DD HH:mm:ss'),
     endTime: dayjs(options.topTask.endtime)
-      .add(1, 'day')
-      .add(-1, 'second')
+      .endOf('day')
       .format('YYYY-MM-DD HH:mm:ss'),
     sceneTypeId: options.sceneTypeId,
     sceneTypeName: options.sceneTypeName,
diff --git a/src/views/fysp/data-product/components/ProdQueryOptCompare.vue b/src/views/fysp/data-product/components/ProdQueryOptCompare.vue
index 6f63038..285fa87 100644
--- a/src/views/fysp/data-product/components/ProdQueryOptCompare.vue
+++ b/src/views/fysp/data-product/components/ProdQueryOptCompare.vue
@@ -82,8 +82,7 @@
       'YYYY-MM-DD HH:mm:ss'
     ),
     endTime: dayjs(formSearch.value.topTask.endtime)
-      .add(1, 'day')
-      .add(-1, 'second')
+      .endOf('day')
       .format('YYYY-MM-DD HH:mm:ss'),
     sceneTypeId: formSearch.value.scenetype.value,
     sceneTypeName: formSearch.value.scenetype.label,
@@ -104,8 +103,7 @@
       'YYYY-MM-DD HH:mm:ss'
     ),
     endTime: dayjs(formSearch2.value.topTask.endtime)
-      .add(1, 'day')
-      .add(-1, 'second')
+      .endOf('day')
       .format('YYYY-MM-DD HH:mm:ss'),
     sceneTypeId: formSearch.value.scenetype.value,
     sceneTypeName: formSearch.value.scenetype.label,
diff --git a/src/views/fysp/data-product/final-data-product/ProdMonInspecReport.vue b/src/views/fysp/data-product/final-data-product/ProdMonInspecReport.vue
new file mode 100644
index 0000000..664fcbe
--- /dev/null
+++ b/src/views/fysp/data-product/final-data-product/ProdMonInspecReport.vue
@@ -0,0 +1,501 @@
+<template>
+  <BaseProdProcess
+    v-model:active="active"
+    @onStep1="onStep1"
+    @onStep2="onStep2"
+    @onStep3="onStep3"
+    :loading="loading"
+  >
+    <template #step1="{ onSearch }">
+      <ProdQueryOptCompare
+        :loading="loading"
+        @submit="onSearch"
+      ></ProdQueryOptCompare>
+    </template>
+    <template #step2="{ contentHeight }">
+      <el-scrollbar :height="contentHeight">
+        <el-card shadow="never"> Nothing </el-card>
+      </el-scrollbar>
+    </template>
+    <template #step3="{ onDownload, queryOpt }">
+      <ProdDownload
+        :loading="loading"
+        :queryOpt="queryOpt"
+        :downloadTypeValid="['2']"
+        defaultDownloadType="2"
+        @submit="onDownload"
+      ></ProdDownload>
+    </template>
+  </BaseProdProcess>
+</template>
+<script setup>
+/**
+ * 鏈堝害鐜板満宸℃煡绠�鎶�
+ */
+import { ref } from 'vue';
+import dayjs from 'dayjs';
+import { exportDocx } from '@/utils/doc';
+import BaseProdProcess from '@/views/fysp/data-product/components/BaseProdProcess.vue';
+import ProdQueryOptCompare from '@/views/fysp/data-product/components/ProdQueryOptCompare.vue';
+import ProdDownload from '@/views/fysp/data-product/components/ProdDownload.vue';
+import dataprodbaseApi from '@/api/fysp/dataprodbaseApi.js';
+import dataprodmiddleApi from '@/api/fysp/dataprodmiddleApi.js';
+import { useProdStepChange } from '@/views/fysp/data-product/prod-step-change.js';
+import {
+  pieChartOption,
+  barChartOption,
+  getChartUrl
+} from '@/utils/echart-util.js';
+import ProdProblemCountSummaryProxy from '@/views/fysp/data-product/middle-data-product/ProdProblemCountSummaryProxy.js';
+
+const { active, changeActive } = useProdStepChange();
+const loading = ref(false);
+const templateParam = {
+  districtName: '',
+  year: '',
+  month: '',
+  compareMonth: '',
+  sceneType: '宸ュ湴',
+  // 宸℃煡鎯呭喌姹囨��
+  sceneCount: 0,
+  pointCount: 0,
+  reviewPointCount: 0,
+  stopSceneCount: 0,
+  completeSceneCount: 0,
+  // 闂绫诲瀷姹囨��
+  problemTypeCount: 0,
+  problemCount: 0,
+  proTypeSum:
+    '閬撹矾鎵皹47涓紙鍗犳湰鏈堥棶棰樻�绘暟40.5%锛夛紝杈冧笂鏈堝鍔犵害3%锛涘嚭鍏ュ彛锛堥亾璺級鎵皹22涓紙鍗犳湰鏈堥棶棰樻�绘暟19.0%锛夛紝杈冧笂鏈堝鍔犵害4%锛�',
+  proTypeSumChartUrl: '',
+  // 闂鏁伴噺姹囨��
+  townCount: 0,
+  topThree:
+    '鍏卞拰鏂拌矾琛楅亾锛�2.4涓級銆佽姺姹熻タ璺閬擄紙2.14涓級銆佹浌瀹舵浮琛楅亾鍜屽ぉ鐩タ璺閬擄紙2.0涓級銆�',
+  problemCountByAreaChartUrl: '',
+  // 璇勪及鎯呭喌姹囨��
+  evalLevelABCountSum: 0, // 瑙勮寖宸ュ湴锛堝惈鍩烘湰瑙勮寖锛夊娆�
+  evalLevelABRatioSum: '0%', // 瑙勮寖鐜�
+  evalLevelABRatioDiff: '闄嶄綆绾�7%', // 杈冧笂鏈堣鑼冪巼鍙樺寲
+  evalLevelCDCountSum: 0, // 涓嶈鑼冪被锛堝惈涓ラ噸涓嶈鑼冿級瀹舵
+  evalLevelCDRatioSum: '0%', // 涓嶈鑼冪巼
+  evalLevelCDRatioDiff: '澧炲姞绾�10%', // 杈冧笂鏈堜笉瑙勮寖鐜囧彉鍖�
+  // 鍚勮闀囧満鏅鑼冩�ф帓琛岋紙杈冨ソ鍦ㄥ墠锛�
+  evaluationByArea: [
+    {
+      index: 1,
+      townName: '鐭抽棬浜岃矾琛楅亾',
+      validSceneCount: 2,
+      evaluationCount: 2,
+      evalLevelACount: 2,
+      evalLevelBCount: 0,
+      evalLevelCCount: 0,
+      evalLevelDCount: 0,
+      evalLevelRatioAB: '100%'
+    }
+  ],
+  // 鍚勮闀囧満鏅鑼冩�ф�昏
+  validSceneCountSum: 0,
+  evaluationCountSum: 0,
+  evalLevelACountSum: 0,
+  evalLevelBCountSum: 0,
+  evalLevelCCountSum: 0,
+  evalLevelDCountSum: 0,
+
+  // 鏁存敼鎯呭喌
+  proChangeRatioSum: '0%', // 鏁翠綋闂鏁存敼鐜�
+  sceneChangeRatioSum: '0%', // 锛堝娆★級鏁存敼鍗犳瘮
+  // 鎵皹闃叉不闂鍙婅鑼冩�ц瘎浼版竻鍗曪紙鍚鏍革級
+  changeAndEvalList: [
+    {
+      index: 1,
+      sceneName: '',
+      address: '',
+      townName: '',
+      score: 0,
+      scoreLevel: '',
+      problemCount: 0,
+      problemList: [
+        {
+          index: 1,
+          problemName: ''
+        }
+      ]
+    }
+  ],
+  // 鏈暣鏀规竻鍗�
+  unchangeSceneList: [
+    {
+      index: 1,
+      sceneName: '',
+      address: '',
+      townName: '',
+      problemCount: 0,
+      problemList: [
+        {
+          index: 1,
+          problemName: ''
+        }
+      ]
+    }
+  ]
+};
+
+function onStep1(opts) {
+  const [opt, compareOpt] = opts;
+  loading.value = true;
+  templateParam.districtName = opt.districtName;
+  templateParam.year = dayjs(opt.startTime).year();
+  templateParam.month = dayjs(opt.startTime).month() + 1;
+  templateParam.compareMonth = dayjs(compareOpt.startTime).month() + 1;
+  templateParam.sceneType = opt.sceneTypeName;
+  const p1 = dataprodmiddleApi.fetchInspectionSummary(opt).then((res) => {
+    if (res.success) {
+      Object.assign(templateParam, res.data);
+    }
+  });
+  const p2 = dataprodmiddleApi
+    .fetchProblemTypeSummary(opt)
+    .then(async (res) => {
+      if (res.success) {
+        const data = res.data.sort((a, b) => b.count - a.count);
+        templateParam.problemTypeCount = data.length;
+        templateParam.problemCount = data.reduce(
+          (acc, cur) => acc + cur.count,
+          0
+        );
+        templateParam.proTypeSum =
+          data
+            .map((item) => {
+              let _ratioDiff;
+              if (item.ratioDiff > 0) {
+                _ratioDiff = `澧炲姞绾�${(Math.abs(item.ratioDiff) * 100).toFixed(
+                  1
+                )}%`;
+              } else if (item.ratioDiff < 0) {
+                _ratioDiff = `鍑忓皯绾�${(Math.abs(item.ratioDiff) * 100).toFixed(
+                  1
+                )}%`;
+              } else {
+                _ratioDiff = '鍩烘湰鎸佸钩';
+              }
+              return `${item.typeName}${item.count}涓紙鍗犳湰鏈堥棶棰樻�绘暟${(
+                item.ratio * 100
+              ).toFixed(1)}%锛夛紝杈冧笂鏈�${_ratioDiff}锛沗;
+            })
+            .join('锛�') + '銆�';
+        templateParam.proTypeSumChartUrl = await genChartProblemTypeSummary(
+          opt,
+          data
+        );
+      }
+    });
+  const p3 = dataprodmiddleApi
+    .fetchProblemCountByArea(opt)
+    .then(async (res) => {
+      if (res.success) {
+        const data = res.data.sort((a, b) => b.ratio - a.ratio);
+        templateParam.townCount = data.length;
+        templateParam.topThree =
+          data
+            .slice(0, 3)
+            .map((item) => `${item.townName}锛�${item.ratio.toFixed(1)}涓級`)
+            .join('銆�') + '銆�';
+
+        const res2 =
+          await dataprodmiddleApi.fetchProblemCountByArea(compareOpt);
+        if (res2.success) {
+          const data2 = res2.data.sort((a, b) => b.ratio - a.ratio);
+          const combineData = ProdProblemCountSummaryProxy.combineData(
+            data,
+            data2
+          );
+          templateParam.problemCountByAreaChartUrl =
+            await genChartProblemCountByArea(opt, compareOpt, combineData);
+        }
+      }
+    });
+  const p4 = dataprodmiddleApi.fetchEvaluationByArea(opt).then(async (res) => {
+    if (res.success) {
+      templateParam.evaluationByArea = [];
+      res.data
+        .sort((a, b) => {
+          return b.evalLevelRatioAB - a.evalLevelRatioAB;
+        })
+        .forEach((item, index) => {
+          templateParam.evaluationByArea.push({
+            index: index + 1,
+            townName: item.townName,
+            validSceneCount: item.validSceneCount,
+            evaluationCount: item.evaluationCount,
+            evalLevelACount: item.evalLevelACount,
+            evalLevelBCount: item.evalLevelBCount,
+            evalLevelCCount: item.evalLevelCCount,
+            evalLevelDCount: item.evalLevelDCount,
+            evalLevelRatioAB: `${(item.evalLevelRatioAB * 100).toFixed(1)}%`
+          });
+          templateParam.validSceneCountSum += item.validSceneCount;
+          templateParam.evaluationCountSum += item.evaluationCount;
+          templateParam.evalLevelACountSum += item.evalLevelACount;
+          templateParam.evalLevelBCountSum += item.evalLevelBCount;
+          templateParam.evalLevelCCountSum += item.evalLevelCCount;
+          templateParam.evalLevelDCountSum += item.evalLevelDCount;
+        });
+      templateParam.evalLevelABCountSum =
+        templateParam.evalLevelACountSum + templateParam.evalLevelBCountSum;
+      templateParam.evalLevelABRatioSum = `${(
+        (templateParam.evalLevelABCountSum / templateParam.evaluationCountSum) *
+        100
+      ).toFixed(1)}%`;
+      templateParam.evalLevelCDCountSum =
+        templateParam.evalLevelCCountSum + templateParam.evalLevelDCountSum;
+      templateParam.evalLevelCDRatioSum = `${(
+        (templateParam.evalLevelCDCountSum / templateParam.evaluationCountSum) *
+        100
+      ).toFixed(1)}%`;
+
+      // 鍜屼笂鏈堟儏鍐佃繘琛屽姣�
+      const res2 = await dataprodmiddleApi.fetchEvaluationByArea(compareOpt);
+      if (res2.success) {
+        const compareData = {
+          validSceneCountSum: 0,
+          evaluationCountSum: 0,
+          evalLevelACountSum: 0,
+          evalLevelBCountSum: 0,
+          evalLevelCCountSum: 0,
+          evalLevelDCountSum: 0
+        };
+        res2.data
+          .sort((a, b) => {
+            return b.evalLevelRatioAB - a.evalLevelRatioAB;
+          })
+          .forEach((item, index) => {
+            compareData.validSceneCountSum += item.validSceneCount;
+            compareData.evaluationCountSum += item.evaluationCount;
+            compareData.evalLevelACountSum += item.evalLevelACount;
+            compareData.evalLevelBCountSum += item.evalLevelBCount;
+            compareData.evalLevelCCountSum += item.evalLevelCCount;
+            compareData.evalLevelDCountSum += item.evalLevelDCount;
+          });
+        const ratioABDiff =
+          ((compareData.evalLevelACountSum + compareData.evalLevelBCountSum) /
+            compareData.evaluationCountSum) *
+            100 -
+          parseFloat(templateParam.evalLevelABRatioSum.replace('%', ''));
+
+        const ratioCDDiff =
+          ((compareData.evalLevelCCountSum + compareData.evalLevelDCountSum) /
+            compareData.evaluationCountSum) *
+            100 -
+          parseFloat(templateParam.evalLevelCDRatioSum.replace('%', ''));
+
+        if (ratioABDiff > 0) {
+          templateParam.evalLevelABRatioDiff = `澧炲姞绾�${Math.abs(
+            ratioABDiff
+          ).toFixed(1)}%`;
+        } else if (ratioABDiff < 0) {
+          templateParam.evalLevelABRatioDiff = `鍑忓皯绾�${Math.abs(
+            ratioABDiff
+          ).toFixed(1)}%`;
+        } else {
+          templateParam.evalLevelABRatioDiff = '鍩烘湰鎸佸钩';
+        }
+        if (ratioCDDiff > 0) {
+          templateParam.evalLevelCDRatioDiff = `澧炲姞绾�${Math.abs(
+            ratioCDDiff
+          ).toFixed(1)}%`;
+        } else if (ratioCDDiff < 0) {
+          templateParam.evalLevelCDRatioDiff = `鍑忓皯绾�${Math.abs(
+            ratioCDDiff
+          ).toFixed(1)}%`;
+        } else {
+          templateParam.evalLevelCDRatioDiff = '鍩烘湰鎸佸钩';
+        }
+      }
+    }
+  });
+  const p5 = dataprodbaseApi.fetchProdInspectionInfo(opt).then(async (res) => {
+    if (res.success) {
+      // 璁$畻闂鏁存敼鐜囧拰鍦烘櫙鏁存敼鍗犳瘮
+      const problemSum = {
+        // 闂鎬绘暟
+        proTotalCount: 0,
+        // 鏁存敼闂鏁�
+        proChangeCount: 0,
+        // 鎬诲贰鏌ョ偣娆℃暟锛堢浉鍚屽満鏅贰鏌ュ娆′篃浼氳绠楋級
+        sceneTotalCount: 0,
+        // 鏁存敼鐐规锛堟垨瀹舵锛夋暟
+        sceneChangeCount: 0
+      };
+
+      templateParam.changeAndEvalList = [];
+      templateParam.unchangeSceneList = [];
+      // 鑾峰彇璇勪及缁撴灉
+      const evalInfo = await dataprodbaseApi.fetchProdEvaluateInfo(opt);
+      const sceneInfo = await dataprodbaseApi.fetchProdSceneInfo(opt);
+      if (evalInfo.success) {
+        const evalData = evalInfo.data.sort(
+          (a, b) =>
+            parseInt(a.evaluate.resultscorebef) -
+            parseInt(b.evaluate.resultscorebef)
+        );
+        evalData.forEach((item, index) => {
+          // 鏌ユ壘瀵瑰簲鐨勫贰鏌ヤ换鍔¤褰�
+          const inspItem = res.data.find(
+            (i) => i.subTask.stguid === item.subTask.stguid
+          );
+          // 鏌ユ壘瀵瑰簲鐨勭洃绠″満鏅俊鎭�
+          const sceneItem = sceneInfo.data.find(
+            (i) => i.scene.guid === item.subTask.scenseid
+          );
+          // 鐢熸垚妯℃澘鍙傛暟
+          const changeAndEvalItem = {
+            sceneName: item.subTask.scensename,
+            address: item.subTask.scenseaddress,
+            townName: item.subTask.townname
+          };
+          // 鏍规嵁鍦烘櫙鐨勮繍钀ョ姸鎬侊紝鍐冲畾鍙傛暟鍐呭
+          if (sceneItem.statusBool) {
+            const obj = {
+              score: parseInt(item.evaluate.resultscorebef),
+              scoreLevel: item.scoreLevel,
+              problemCount: inspItem.problems.length,
+              // problemsDes: inspItem.problems
+              //   .map((p, i) => `${i + 1}銆�${p.problemname}`)
+              //   .join('\r\n'),
+              problemList:
+                inspItem.problems.length > 0
+                  ? inspItem.problems.map((p, i) => {
+                      return {
+                        index: i + 1,
+                        problemName: p.problemname
+                      };
+                    })
+                  : [
+                      {
+                        index: '',
+                        problemName: '妫�鏌ュ綋澶╃幇鍦烘棤鏄庢樉闂'
+                      }
+                    ]
+            };
+            Object.assign(changeAndEvalItem, obj);
+
+            // 鏇存柊闂鏁存敼鐜囧拰鍦烘櫙鏁存敼鍗犳瘮
+            problemSum.proTotalCount += inspItem.problems.length;
+            problemSum.sceneTotalCount += 1;
+            const unchangeProList = inspItem.problems.filter(
+              (p) => !p.ischanged
+            );
+            problemSum.proChangeCount +=
+              inspItem.problems.length - unchangeProList.length;
+            // 闄や簡鍏ㄩ儴闂閮芥病鏁存敼鐨勫満鏅紝閮界畻鏁存敼鐐规锛堟垨瀹舵锛夋暟
+            problemSum.sceneChangeCount +=
+              inspItem.problems.length > 0 &&
+              inspItem.problems.length == unchangeProList.length
+                ? 0
+                : 1;
+
+            if (unchangeProList.length > 0) {
+              templateParam.unchangeSceneList.push({
+                index: templateParam.unchangeSceneList.length + 1,
+                sceneName: item.subTask.scensename,
+                address: item.subTask.scenseaddress,
+                townName: item.subTask.townname,
+                problemCount: unchangeProList.length,
+                problemList: unchangeProList.map((p, i) => {
+                  return {
+                    index: i + 1,
+                    problemName: p.problemname
+                  };
+                })
+              });
+            }
+          } else {
+            const obj = {
+              score: '/',
+              scoreLevel: '/',
+              problemCount: '/',
+              // problemsDes: inspItem.problems
+              //   .map((p, i) => `${i + 1}銆�${p.problemname}`)
+              //   .join('\r\n'),
+              problemList: [
+                {
+                  index: '',
+                  problemName: sceneItem.status
+                }
+              ]
+            };
+            Object.assign(changeAndEvalItem, obj);
+          }
+          templateParam.changeAndEvalList.push({
+            index: index + 1,
+            ...changeAndEvalItem
+          });
+        });
+      }
+
+      templateParam.proChangeRatioSum = `${(
+        (problemSum.proChangeCount / problemSum.proTotalCount) *
+        100
+      ).toFixed(1)}%`;
+      templateParam.sceneChangeRatioSum = `${(
+        (problemSum.sceneChangeCount / problemSum.sceneTotalCount) *
+        100
+      ).toFixed(1)}%`;
+    }
+  });
+  Promise.all([p1, p2, p3, p4, p5])
+    .then(() => {
+      changeActive();
+      changeActive();
+    })
+    .finally(() => {
+      loading.value = false;
+    });
+}
+
+function onStep2() {
+  changeActive();
+  // ElMessage.error('鏆備笉鎻愪緵涓嬭浇');
+}
+
+function onStep3(val) {
+  if (val.downloadType == '2') {
+    loading.value = true;
+    exportDocx(
+      '/鎵皹姹℃煋鐩戠绠�鎶ユā鏉�.docx',
+      templateParam,
+      `${templateParam.districtName}${
+        templateParam.month
+      }鏈堟壃灏樻薄鏌撶洃绠$畝鎶�-${dayjs().format('YYYYMMDD')}.docx`,
+      {
+        horizontalHeight: 300,
+        verticalWidth: 600,
+        scale: 2
+      }
+    ).finally(() => (loading.value = false));
+  }
+}
+
+function genChartProblemTypeSummary(opt, data) {
+  const startTime = dayjs(opt.startTime).format('YYYY骞碝M鏈�');
+  const option = pieChartOption();
+  option.title.text = `${startTime}${opt.districtName}鎵皹姹℃煋闂绫诲瀷鍗犳瘮`;
+  option.legend.data = data.map((item) => item.typeName);
+  option.series[0].name = '闂绫诲瀷';
+  option.series[0].data = data.map((item) => ({
+    name: item.typeName,
+    value: item.count
+  }));
+  return getChartUrl(option);
+}
+
+function genChartProblemCountByArea(opt1, opt2, combineData) {
+  const option = ProdProblemCountSummaryProxy.genChartOption(
+    opt1,
+    opt2,
+    combineData
+  );
+  return getChartUrl(option);
+}
+</script>
diff --git a/src/views/fysp/data-product/middle-data-product/ProdProblemCountSummary.vue b/src/views/fysp/data-product/middle-data-product/ProdProblemCountSummary.vue
index a743ccc..8baf62d 100644
--- a/src/views/fysp/data-product/middle-data-product/ProdProblemCountSummary.vue
+++ b/src/views/fysp/data-product/middle-data-product/ProdProblemCountSummary.vue
@@ -7,7 +7,10 @@
     :loading="loading"
   >
     <template #step1="{ onSearch }">
-      <ProdQueryOptCompare :loading="loading" @submit="onSearch"></ProdQueryOptCompare>
+      <ProdQueryOptCompare
+        :loading="loading"
+        @submit="onSearch"
+      ></ProdQueryOptCompare>
     </template>
     <template #step2="{ contentHeight }">
       <el-scrollbar :height="contentHeight">
@@ -78,11 +81,12 @@
 import * as echarts from 'echarts';
 import dayjs from 'dayjs';
 import BaseProdProcess from '@/views/fysp/data-product/components/BaseProdProcess.vue';
+import ProdQueryOptCompare from '@/views/fysp/data-product/components/ProdQueryOptCompare.vue';
 import dataprodmiddleApi from '@/api/fysp/dataprodmiddleApi.js';
 import { conversionFromTable } from '@/utils/excel';
 import { useProdStepChange } from '@/views/fysp/data-product/prod-step-change.js';
 import { barChartOption, downloadChartImage } from '@/utils/echart-util.js';
-import ProdQueryOptCompare from '@/views/fysp/data-product/components/ProdQueryOptCompare.vue';
+import Proxy from '@/views/fysp/data-product/middle-data-product/ProdProblemCountSummaryProxy.js';
 
 const { active, changeActive } = useProdStepChange();
 const loading = ref(false);
@@ -92,18 +96,19 @@
 let chart;
 
 const tableData = computed(() => {
-  return tableData1.value.map((item) => {
-    const last = tableData2.value.find(
-      (item2) => item2.townCode === item.townCode
-    );
-    item.ratio = Math.round(item.ratio * 10) / 10 || 0;
-    return {
-      ...item,
-      lastSceneCount: last?.sceneCount || 0,
-      lastProblemCount: last?.problemCount || 0,
-      lastRatio: Math.round((last?.ratio || 0) * 10) / 10 || 0
-    };
-  });
+  // return tableData1.value.map((item) => {
+  //   const last = tableData2.value.find(
+  //     (item2) => item2.townCode === item.townCode
+  //   );
+  //   item.ratio = Math.round(item.ratio * 10) / 10 || 0;
+  //   return {
+  //     ...item,
+  //     lastSceneCount: last?.sceneCount || 0,
+  //     lastProblemCount: last?.problemCount || 0,
+  //     lastRatio: Math.round((last?.ratio || 0) * 10) / 10 || 0
+  //   };
+  // });
+  return Proxy.combineData(tableData1.value, tableData2.value);
 });
 
 function onStep1(opts) {
@@ -147,39 +152,40 @@
   if (chart == undefined) {
     chart = echarts.init(chartRef.value);
   }
-  const year = dayjs(opt1.startTime).year();
-  const month1 = dayjs(opt1.startTime).month() + 1;
-  const month2 = dayjs(opt2.startTime).month() + 1;
-  const time = `${year}骞�${month1}鏈堛��${month2}鏈坄;
-  const option = barChartOption();
-  option.title.text = `${time}${opt1.districtName}鍚勮閬擄紙闀囷級${opt1.sceneTypeName}鎵皹姹℃煋闂鏁板潎鍊煎姣擿;
+  // const year = dayjs(opt1.startTime).year();
+  // const month1 = dayjs(opt1.startTime).month() + 1;
+  // const month2 = dayjs(opt2.startTime).month() + 1;
+  // const time = `${year}骞�${month1}鏈堛��${month2}鏈坄;
+  // const option = barChartOption();
+  // option.title.text = `${time}${opt1.districtName}鍚勮閬擄紙闀囷級${opt1.sceneTypeName}鎵皹姹℃煋闂鏁板潎鍊煎姣擿;
 
-  option.xAxis.name = '琛楅亾锛堥晣锛�';
-  option.xAxis.data = tableData.value.map((item) => item.townName);
-  option.yAxis.name = '闂鏁板潎鍊�';
+  // option.xAxis.name = '琛楅亾锛堥晣锛�';
+  // option.xAxis.data = tableData.value.map((item) => item.townName);
+  // option.yAxis.name = '闂鏁板潎鍊�';
 
-  option.series = [
-    {
-      name: `${month1}鏈坄,
-      type: 'bar', // 鍥捐〃绫诲瀷鏀逛负鏌辩姸鍥�
-      data: tableData.value.map((item) => item.ratio),
-      label: {
-        show: true,
-        position: 'top', // 鏍囩鏄剧ず鍦ㄦ煴瀛愰《閮�
-        formatter: '{c}' // 鏍囩鏍煎紡锛氭暟閲�
-      }
-    },
-    {
-      name: `${month2}鏈坄,
-      type: 'bar', // 鍥捐〃绫诲瀷鏀逛负鏌辩姸鍥�
-      data: tableData.value.map((item) => item.lastRatio),
-      label: {
-        show: true,
-        position: 'top', // 鏍囩鏄剧ず鍦ㄦ煴瀛愰《閮�
-        formatter: '{c}' // 鏍囩鏍煎紡锛氭暟閲�
-      }
-    }
-  ];
+  // option.series = [
+  //   {
+  //     name: `${month1}鏈坄,
+  //     type: 'bar', // 鍥捐〃绫诲瀷鏀逛负鏌辩姸鍥�
+  //     data: tableData.value.map((item) => item.ratio),
+  //     label: {
+  //       show: true,
+  //       position: 'top', // 鏍囩鏄剧ず鍦ㄦ煴瀛愰《閮�
+  //       formatter: '{c}' // 鏍囩鏍煎紡锛氭暟閲�
+  //     }
+  //   },
+  //   {
+  //     name: `${month2}鏈坄,
+  //     type: 'bar', // 鍥捐〃绫诲瀷鏀逛负鏌辩姸鍥�
+  //     data: tableData.value.map((item) => item.lastRatio),
+  //     label: {
+  //       show: true,
+  //       position: 'top', // 鏍囩鏄剧ず鍦ㄦ煴瀛愰《閮�
+  //       formatter: '{c}' // 鏍囩鏍煎紡锛氭暟閲�
+  //     }
+  //   }
+  // ];
+  const option = Proxy.genChartOption(opt1, opt2, tableData.value);
   chart.setOption(option);
 }
 
diff --git a/src/views/fysp/data-product/middle-data-product/ProdProblemCountSummaryProxy.js b/src/views/fysp/data-product/middle-data-product/ProdProblemCountSummaryProxy.js
new file mode 100644
index 0000000..aaefc9c
--- /dev/null
+++ b/src/views/fysp/data-product/middle-data-product/ProdProblemCountSummaryProxy.js
@@ -0,0 +1,83 @@
+/**
+ * ProdProblemCountSummary 閫昏緫浠g悊
+ */
+import dayjs from 'dayjs';
+import { barChartOption, downloadChartImage } from '@/utils/echart-util.js';
+
+export default {
+  /**
+   * 鍚堝苟鏁版嵁
+   * @param {*} data1
+   * @param {*} data2
+   * @returns
+   */
+  combineData(data1, data2) {
+    return data1.map((item) => {
+      const last = data2.find((item2) => item2.townCode === item.townCode);
+      item.ratio = Math.round(item.ratio * 10) / 10 || 0;
+      return {
+        ...item,
+        lastSceneCount: last?.sceneCount || 0,
+        lastProblemCount: last?.problemCount || 0,
+        lastRatio: Math.round((last?.ratio || 0) * 10) / 10 || 0
+      };
+    });
+  },
+
+  
+  /**
+   * 鐢熸垚鎵皹姹℃煋闂鏁板姣旀煴鐘跺浘閰嶇疆
+   * @param {Object} opt1 - 绗竴涓椂闂存鐨勯厤缃璞★紝鍖呭惈startTime(寮�濮嬫椂闂�)銆乨istrictName(鍖哄煙鍚嶇О)銆乻ceneTypeName(鍦烘櫙绫诲瀷鍚嶇О)
+   * @param {Object} opt2 - 绗簩涓椂闂存鐨勯厤缃璞★紝鍖呭惈startTime(寮�濮嬫椂闂�)
+   * @param {Array} data - 鏁版嵁鏁扮粍锛屾瘡涓厓绱犲寘鍚玹ownName(琛楅亾鍚嶇О)銆乺atio(褰撳墠闂鏁板潎鍊�)銆乴astRatio(瀵规瘮闂鏁板潎鍊�)
+   * @returns {Object} ECharts鍥捐〃閰嶇疆瀵硅薄
+   */
+    genChartOption(opt1, opt2, data) {
+      // 浠庣涓�涓椂闂撮厤缃腑鑾峰彇骞翠唤
+      const year = dayjs(opt1.startTime).year();
+      // 浠庣涓�涓椂闂撮厤缃腑鑾峰彇鏈堜唤锛堟敞鎰忥細JavaScript鐨勬湀浠芥槸0-11锛屾墍浠ュ姞1锛�
+      const month1 = dayjs(opt1.startTime).month() + 1;
+      // 浠庣浜屼釜鏃堕棿閰嶇疆涓幏鍙栨湀浠�
+      const month2 = dayjs(opt2.startTime).month() + 1;
+      // 鏍煎紡鍖栨椂闂村瓧绗︿覆锛岀敤浜庡浘琛ㄦ爣棰�
+      const time = `${year}骞�${month2}鏈堛��${month1}鏈坄;
+      // 鑾峰彇鍩虹鏌辩姸鍥鹃厤缃ā鏉�
+      const option = barChartOption();
+      // 璁剧疆鍥捐〃鏍囬锛屽寘鍚椂闂存銆佸尯鍩熷悕绉般�佸満鏅被鍨嬬瓑淇℃伅
+      option.title.text = `${time}${opt1.districtName}鍚勮閬擄紙闀囷級${opt1.sceneTypeName}鎵皹姹℃煋闂鏁板潎鍊煎姣擿;
+  
+      // 璁剧疆X杞村悕绉颁负琛楅亾锛堥晣锛�
+      option.xAxis.name = '琛楅亾锛堥晣锛�';
+      // 璁剧疆X杞存暟鎹负鍚勮閬撳悕绉�
+      option.xAxis.data = data.map((item) => item.townName);
+      // 璁剧疆Y杞村悕绉颁负闂鏁板潎鍊�
+      option.yAxis.name = '闂鏁板潎鍊�';
+  
+      // 閰嶇疆鍥捐〃鏁版嵁绯诲垪
+      option.series = [
+        {
+          name: `${month1}鏈坄, // 褰撳墠鏈堜唤鏁版嵁绯诲垪鍚嶇О
+          type: 'bar', // 鍥捐〃绫诲瀷涓烘煴鐘跺浘
+          data: data.map((item) => item.ratio), // 褰撳墠鏈堜唤鍚勮閬撻棶棰樻暟鍧囧��
+          label: {
+            show: true, // 鏄剧ず鏁版嵁鏍囩
+            position: 'top', // 鏍囩鏄剧ず鍦ㄦ煴瀛愰《閮�
+            formatter: '{c}' // 鏍囩鏍煎紡锛氱洿鎺ユ樉绀烘暟鍊�
+          }
+        },
+        {
+          name: `${month2}鏈坄, // 瀵规瘮鏈堜唤鏁版嵁绯诲垪鍚嶇О
+          type: 'bar', // 鍥捐〃绫诲瀷涓烘煴鐘跺浘
+          data: data.map((item) => item.lastRatio), // 瀵规瘮鏈堜唤鍚勮閬撻棶棰樻暟鍧囧��
+          label: {
+            show: true, // 鏄剧ず鏁版嵁鏍囩
+            position: 'top', // 鏍囩鏄剧ず鍦ㄦ煴瀛愰《閮�
+            formatter: '{c}' // 鏍囩鏍煎紡锛氱洿鎺ユ樉绀烘暟鍊�
+          }
+        }
+      ];
+      
+      // 杩斿洖瀹屾暣鐨勫浘琛ㄩ厤缃璞�
+      return option;
+    }
+};
\ No newline at end of file
diff --git a/src/views/fysp/support/JingAnNightConstruction.vue b/src/views/fysp/support/JingAnNightConstruction.vue
index 2c2f90f..ec5d544 100644
--- a/src/views/fysp/support/JingAnNightConstruction.vue
+++ b/src/views/fysp/support/JingAnNightConstruction.vue
@@ -39,24 +39,39 @@
       <el-table-column prop="ncCityName" label="甯�" width="90" /> -->
       <!-- <el-table-column prop="ncDistrictName" label="鍖哄幙" width="90" /> -->
       <!-- <el-table-column prop="townname" label="琛楅亾" width="110" /> -->
+       <el-table-column
+        prop="ncFileName"
+        label="澶滄柦鏂囦欢"
+        width="170"
+      >
+        <template #default="{ row }">
+          <el-link
+            :underline="true"
+            type="primary"
+            :href="row.ncUrl"
+            target="_blank"
+            >{{ row.ncFileName }}</el-link
+          >
+        </template>
+      </el-table-column>
       <el-table-column
         prop="ncConstructionUnit"
         label="鏂藉伐鍗曚綅"
         min-width="100"
       />
-      <el-table-column prop="ncPerson" label="鐢宠浜�" width="110" />
+      <el-table-column prop="ncPerson" label="鐢宠浜�" width="70" />
       <el-table-column prop="ncApplyContent" label="鐢宠鍐呭" width="110" />
-      <el-table-column prop="ncStartDate" label="鐢宠鏃堕棿" width="110">
+      <el-table-column prop="ncStartDate" label="鐢宠鏃堕棿" width="100">
         <template #default="{ row }">
           {{ $fm.formatYMD(row.ncCreateTime) }}
         </template>
       </el-table-column>
-      <el-table-column prop="ncStartDate" label="宸ユ湡寮�濮�" width="110">
+      <el-table-column prop="ncStartDate" label="宸ユ湡寮�濮�" width="100">
         <template #default="{ row }">
           {{ $fm.formatYMD(row.ncStartDate) }}
         </template>
       </el-table-column>
-      <el-table-column prop="ncEndDate" label="宸ユ湡缁撴潫" width="110">
+      <el-table-column prop="ncEndDate" label="宸ユ湡缁撴潫" width="100">
         <template #default="{ row }">
           {{ $fm.formatYMD(row.ncEndDate) }}
         </template>
@@ -70,7 +85,7 @@
         <template #default="{ row }">
           <el-text
             :loading="row._loading"
-            :type="row._user ? 'primary' : 'danger'"
+            :type="row._user ? 'success' : 'danger'"
             >{{ row._user ? row._user.realname : '鏈尮閰�' }}</el-text
           >
         </template>

--
Gitblit v1.9.3