riku
2025-10-11 925b485e661682a2fcfc8b8a47a8148d16ec818e
新增最终数据产品月度巡查简报
已修改9个文件
已添加3个文件
823 ■■■■■ 文件已修改
public/扬尘污染监管简报模板.docx 补丁 | 查看 | 原始文档 | blame | 历史
src/constants/menu.js 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/index.js 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/echart-util.js 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/components/BaseProdProcess.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/components/ProdDownload.vue 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/components/ProdQueryOpt.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/components/ProdQueryOptCompare.vue 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/final-data-product/ProdMonInspecReport.vue 501 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/middle-data-product/ProdProblemCountSummary.vue 96 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/middle-data-product/ProdProblemCountSummaryProxy.js 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/support/JingAnNightConstruction.vue 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
public/Ñï³¾ÎÛȾ¼à¹Ü¼ò±¨Ä£°å.docx
Binary files differ
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: '巡查月度简报'
      },
    ]
  },
  // {
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'
                      ),
                  },
                ]
              }
            ]
          },
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. åˆå§‹åŒ–echarts实例
  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 };
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
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
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,
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,
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å¹´MM月');
  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>
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);
}
src/views/fysp/data-product/middle-data-product/ProdProblemCountSummaryProxy.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,83 @@
/**
 * ProdProblemCountSummary é€»è¾‘代理
 */
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(开始时间)、districtName(区域名称)、sceneTypeName(场景类型名称)
   * @param {Object} opt2 - ç¬¬äºŒä¸ªæ—¶é—´æ®µçš„配置对象,包含startTime(开始时间)
   * @param {Array} data - æ•°æ®æ•°ç»„,每个元素包含townName(街道名称)、ratio(当前问题数均值)、lastRatio(对比问题数均值)
   * @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;
    }
};
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>