riku
2025-09-30 7e89ae52ea3b97429a116bd3d8e4b2ba05e02164
2025.9.30 新增单次走航报告自动生成功能
已修改3个文件
已添加2个文件
298 ■■■■■ 文件已修改
public/underway_mission_report.docx 补丁 | 查看 | 原始文档 | blame | 历史
src/api/dataAnalysisApi.js 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/mission/MissionManage.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/mission/missionReportDownload.js 250 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/historymode/component/MissionReport.vue 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
public/underway_mission_report.docx
Binary files differ
src/api/dataAnalysisApi.js
@@ -33,6 +33,14 @@
      .then((res) => res.data);
  },
  fetchOneMissionSummary(missionCode) {
    return $http
      .get(`air/analysis/report/missionSummary/one`, {
        params: { missionCode }
      })
      .then((res) => res.data);
  },
  /**
   * èŽ·å–èµ°èˆªä»»åŠ¡æ¸…å•
   * @param {*} startTime å¼€å§‹æ—¶é—´ï¼Œæ ¼å¼YYYY-MM-DD HH:mm:ss
@@ -63,6 +71,14 @@
      .then((res) => res.data);
  },
  fetchOneMissionDetail(missionCode) {
    return $http
      .get(`air/analysis/report/missionDetail/one`, {
        params: { missionCode }
      })
      .then((res) => res.data);
  },
  /**
   * èŽ·å–èµ°èˆªå…¸åž‹éšæ‚£åŒºåŸŸ
   * @param {*} startTime å¼€å§‹æ—¶é—´ï¼Œæ ¼å¼YYYY-MM-DD HH:mm:ss
@@ -78,6 +94,14 @@
      .then((res) => res.data);
  },
  fetchOneClueByRiskArea(missionCode) {
    return $http
      .get(`air/analysis/report/clueByRiskArea/one`, {
        params: { missionCode }
      })
      .then((res) => res.data);
  },
  fetchGridFusion({ startTime, endTime, area, factorTypes }) {
    return $http
      .post(`air/analysis/report/gridFusion`, area, {
src/components/mission/MissionManage.vue
@@ -60,14 +60,14 @@
                class="el-button-custom"
                @click="deleteMission(row)"
              ></el-button>
              <!-- <el-button
              <el-button
                :loading="row.downloadLoading"
                type="primary"
                size="small"
                icon="Document"
                class="el-button-custom"
                @click="downloadReport(row)"
              ></el-button> -->
              ></el-button>
            </template>
          </el-table-column>
        </el-table>
@@ -118,6 +118,7 @@
import { mapStores } from 'pinia';
import { useMissionStore } from '@/stores/mission';
import { useFetchData } from '@/composables/fetchData';
import { downloadReport } from '@/components/mission/missionReportDownload.js';
export default {
  setup() {
@@ -163,9 +164,10 @@
    },
    downloadReport(row) {
      row.downloadLoading = true;
      missionApi
        .downloadReport(row.missionCode)
        .finally(() => (row.downloadLoading = false));
      // missionApi
      //   .downloadReport(row.missionCode)
      //   .finally(() => (row.downloadLoading = false));
      downloadReport(row).finally(() => (row.downloadLoading = false));
    },
    // eslint-disable-next-line no-unused-vars
    timeFormatter(row, col, cellValue, index) {
src/components/mission/missionReportDownload.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,250 @@
import moment from 'moment';
import { exportDocx } from '@/utils/doc';
import dataAnalysisApi from '@/api/dataAnalysisApi';
import { FactorDatas } from '@/model/FactorDatas';
import factorDataParser from '@/utils/chart/factor-data-parser';
import chartToImg from '@/utils/chart/chart-to-img';
const groupBy = (array, func) => {
  const groups = {};
  array.forEach((item) => {
    const key = func(item);
    if (!groups[key]) {
      groups[key] = [];
    }
    groups[key].push(item);
  });
  return Object.keys(groups).map((key) => ({
    key,
    values: groups[key]
  }));
};
const templateParam = () => {
  return {
    // èµ°èˆªä»»åŠ¡æƒ…å†µ
    cityName: '上海市',
    districtName: '长宁区',
    createTime: '2024å¹´9月9日',
    missionTime: '2024å¹´9月6日',
    missionDate: '9月6日',
    startTime: '13:00',
    endTime: '17:00',
    missionPeriod: '下午',
    region: '仙霞站',
    radius: '2公里',
    // èµ°èˆªå½“日天气状况
    pollutionDegree: '优',
    aqi: 46,
    mainFactor: '无',
    factorAvgDes:
      'PM2.5(46μg/m3)、PM10(66μg/m3)、O3-8H(138μg/m3)、SO2(15μg/m3)、NO2(41μg/m3)、CO(0.7mg/m3)',
    weather: '多云,18-28℃,东南风2级',
    // èµ°èˆªè·¯çº¿å›¾
    roadMapUrl: '',
    // èµ°èˆªç›‘测因子情况
    focusFactor: 'PM2.5、PM10、VOCs、O3',
    mainFactorStatus:
      '走航期间,PM2.5、PM10等监测因子浓度均值高于长宁空气质量背景,',
    // mainFactorStatus:'走航期间,PM2.5、PM10等监测因子浓度均值均处于优,与长宁空气质量背景基本一致,',
    factorDetailList: [
      {
        factor: 'PM2.5',
        avgValue: 22,
        minValue: 19,
        maxValue: 25
      }
    ],
    clueByAreaList: [
      {
        _index: 1,
        _area: '某某区域周边',
        clueByFactorList: [
          {
            factor: 'PM₂.₅',
            clues: [
              {
                _factorNames: 'PM2.5',
                _time: '10:22:28 - 10:22:34',
                _riskRegion: '长宁区清溪路可乐东路',
                _exceptionType: '快速上升',
                _chart: '',
                _conclusion:
                  '在10:22:28至10:22:34之间,出现快速上升,VOC最低值为135.95μg/m³,最高值为135.95μg/m³,均值为135.95μg/m³,发现3个风险源,包含2个加油站,1个汽修。',
                _scenes:
                  '1.上海依德汽车维修有限公司,汽修企业,位于上海市长宁区北虹路1079号,距仙霞站1887米。\r\n……'
              }
            ]
          }
        ]
      }
    ]
  };
};
function genMission(param, mission) {
  param.cityName = mission.cityName;
  param.districtName = mission.districtName;
  param.createTime = moment().format('YYYY-MM-DD');
  param.missionTime = moment(mission.startTime).format('YYYYå¹´MM月DD日');
  param.missionDate = moment(mission.startTime).format('MM月DD日');
  param.startTime = moment(mission.startTime).format('HH:mm');
  param.endTime = moment(mission.endTime).format('HH:mm');
  // æ ¹æ®startTime计算所处时段
  const hour = moment(mission.startTime).hour();
  if (hour >= 0 && hour < 6) {
    param.missionPeriod = '凌晨';
  } else if (hour >= 6 && hour < 9) {
    param.missionPeriod = '早上';
  } else if (hour >= 9 && hour < 12) {
    param.missionPeriod = '上午';
  } else if (hour >= 12 && hour < 14) {
    param.missionPeriod = '中午';
  } else if (hour >= 14 && hour < 18) {
    param.missionPeriod = '下午';
  } else if (hour >= 18 && hour < 22) {
    param.missionPeriod = '晚上';
  } else {
    param.missionPeriod = '深夜';
  }
  param.region = mission.region;
  param.radius = mission.radius ? `${mission.radius}公里` : '';
  // èµ°èˆªå½“日天气状况
  // param.pollutionDegree = mission.pollutionDegree;
  // param.aqi = mission.aqi;
  // param.mainFactor = mission.mainFactor;
  // param.factorAvgDes = mission.factorAvgDes;
  // param.weather = mission.weather;
}
function genMissionSummary(param, mission) {
  return dataAnalysisApi
    .fetchOneMissionSummary(mission.missionCode)
    .then((res) => {
      param.focusRegion = res.data.focusRegion.join('、');
    });
}
function genRoadMap(param, mission) {}
function genFactorDetail(param, mission, focusFactor) {
  return dataAnalysisApi
    .fetchOneMissionDetail(mission.missionCode)
    .then((res) => {
      param.focusFactor = focusFactor.join('、');
      param.mainFactorStatus =
        '走航期间,PM2.5、PM10等监测因子浓度均值高于长宁空气质量背景,';
      param.factorDetailList = res.data.dataStatistics
        .filter((item) => {
          return focusFactor.includes(item.factor);
        })
        .forEach((item) => {
          item.avgValue = item.avgValue.toFixed(2);
          item.minValue = item.minValue.toFixed(2);
          item.maxValue = item.maxValue.toFixed(2);
        });
    });
}
function genClueByRiskArea(param, mission) {
  return dataAnalysisApi
    .fetchOneClueByRiskArea(mission.missionCode)
    .then((res) => {
      const clues = res.data.flatMap((item) =>
        item.clueByFactorList.flatMap((cbf) => cbf.clues)
      );
      param.clueByAreaList = groupBy(
        clues,
        (clue) => clue.pollutedArea.address
      ).map((item) => {
        const { key: area, values: clues } = item;
        return {
          _area: area,
          clues: clues.map((clue) => {
            return {
              _factorNames: Object.keys(clue.pollutedData.statisticMap)
                .map((e) => e)
                .join('、'),
              _time:
                moment(clue.pollutedData.startTime).format('HH:mm:ss') +
                ' - ' +
                moment(clue.pollutedData.endTime).format('HH:mm:ss'),
              _riskRegion: clue.pollutedArea.address
                ? clue.pollutedArea.address
                : '',
              _exceptionType: clue.pollutedData.exception,
              _images: generateChartImg(clue.pollutedData),
              _conclusion: clue.pollutedSource.conclusion,
              _scenes:
                clue.pollutedSource.sceneList.length > 0
                  ? clue.pollutedSource.sceneList
                      .map(
                        (s, index) =>
                          `${index + 1}. ${s.name},${s.type},位于${s.location},距${s.closestStation.name}${parseInt(s.length)}米;`
                      )
                      .join('\n')
                  : '无'
            };
          })
        };
      });
    });
}
function generateChartImg(pollutedData) {
  const exceptionIndexArr = [];
  pollutedData.dataVoList.forEach((e) => {
    const i = pollutedData.historyDataList.findIndex((v) => v.time == e.time);
    exceptionIndexArr.push([i - 1 < 0 ? 0 : i - 1, i]);
  });
  const factorDatas = new FactorDatas();
  const images = [];
  factorDatas.setData(pollutedData.historyDataList, 0, () => {
    for (const key in pollutedData.statisticMap) {
      const value = pollutedData.statisticMap[key];
      const _chartOptions = factorDataParser.parseData(factorDatas, [
        {
          label: value.factorName,
          name: value.factorName,
          value: value.factorId + ''
        }
      ]);
      _chartOptions.forEach((o) => {
        images.push({
          url: chartToImg.generateEchartsImage(o, exceptionIndexArr, 20)
        });
      });
    }
  });
  return images;
}
async function genTemplateParams(mission, focusFactor) {
  const param = templateParam();
  await genMission(param, mission);
  await genMissionSummary(param, mission);
  await genRoadMap(param, mission);
  await genFactorDetail(param, mission, focusFactor);
  await genClueByRiskArea(param, mission);
  Object.keys(param).forEach((key) => {
    if (typeof param[key] === 'string') {
      param[key] = param[key]?.trim() || '';
    }
  });
  console.log('genTemplateParams', param);
  return param;
}
function downloadReport(mission, focusFactor = ['PM25', 'PM10']) {
  return genTemplateParams(mission, focusFactor).then((param) => {
    exportDocx('/underway_mission_report.docx', param, `走航任务报告.docx`, {
      horizontalHeight: 250,
      verticalWidth: 568,
      scale: 2
    });
  });
}
export { downloadReport };
src/views/historymode/component/MissionReport.vue
@@ -23,19 +23,19 @@
        >
          ä¸‹è½½æŠ¥å‘Š
        </el-button>
        <el-button
        <!-- <el-button
          type="primary"
          class="el-button-custom"
          @click="handleGenerateImg"
          :loading="docLoading"
        >
          ç”Ÿæˆå›¾ç‰‡
        </el-button>
        </el-button> -->
      </el-form-item>
      <el-form-item>
      <!-- <el-form-item>
        <el-image :src="base64Url" fit="fill" :preview-src-list="[base64Url]" />
      </el-form-item>
      <el-form-item>
      </el-form-item> -->
      <!-- <el-form-item>
        <el-button
          type="primary"
          class="el-button-custom"
@@ -53,7 +53,7 @@
            />
          </el-form-item>
        </el-form-item>
      </el-form-item>
      </el-form-item> -->
    </el-form>
  </CardDialog>
</template>