riku
2025-09-19 58c0f11fe2f23a1be2dec768f9ac02107301a634
2025.9.19 数据产品(待完成)
已修改15个文件
已添加10个文件
已重命名4个文件
1095 ■■■■ 文件已修改
src/components.d.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/search-option/FYOptionLocation.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/search-option/FYOptionTopTask.vue 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/table/FYTable.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/constants/menu.js 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/index copy.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/index.js 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/echart-util.js 184 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/excel.js 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/ProdLawEnforceList.vue 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/base-data-product/ManageBaseProd.vue 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/base-data-product/ProdEvaluationInfo.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/base-data-product/ProdInspectionInfo.vue 40 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/base-data-product/ProdMonitorDataInfo.vue 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/base-data-product/ProdSceneInfo.vue 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/components/BaseProdProcess.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/components/CompProdManage.vue 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/components/CompProdTextGenerator.vue 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/components/ProdDownload.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/components/ProdQueryOpt.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/components/ProdQueryOptCompare.vue 102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/middle-data-product/ManageMiddleProd.vue 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/middle-data-product/ProdEvaluationSummary.vue 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/middle-data-product/ProdInspectionSummary.vue 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/middle-data-product/ProdMonitorDeviceSummary.vue 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/middle-data-product/ProdProblemCountSummary.vue 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/middle-data-product/ProdProblemTypeSummary.vue 127 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/support/JingAnNightConstruction.vue 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components.d.ts
@@ -13,6 +13,7 @@
    CompGenericWrapper: typeof import('./components/CompGenericWrapper.vue')['default']
    CompQuickSet: typeof import('./components/search-option/CompQuickSet.vue')['default']
    Content: typeof import('./components/core/Content.vue')['default']
    copy: typeof import('./components/search-option/FYOptionScene copy.vue')['default']
    ElAffix: typeof import('element-plus/es')['ElAffix']
    ElAside: typeof import('element-plus/es')['ElAside']
    ElAvatar: typeof import('element-plus/es')['ElAvatar']
@@ -92,6 +93,7 @@
    FYOptionSupervisionStatus: typeof import('./components/search-option/FYOptionSupervisionStatus.vue')['default']
    FYOptionText: typeof import('./components/search-option/base/FYOptionText.vue')['default']
    FYOptionTime: typeof import('./components/search-option/FYOptionTime.vue')['default']
    FYOptionTopTask: typeof import('./components/search-option/FYOptionTopTask.vue')['default']
    FYOptionUserType: typeof import('./components/search-option/FYOptionUserType.vue')['default']
    FYPageHeader: typeof import('./components/head/FYPageHeader.vue')['default']
    FYReconfrimButton: typeof import('./components/button/FYReconfrimButton.vue')['default']
src/components/search-option/FYOptionLocation.vue
@@ -1,13 +1,13 @@
<template>
  <el-form-item :label="placeholder" :prop="prop">
    <el-cascader
      v-bind="$attrs"
      :model-value="formatedValue"
      @change="handleChange"
      :options="locations"
      :placeholder="placeholder"
      :props="optionProps"
      style="width: 320px"
      v-bind="$attrs"
    />
  </el-form-item>
</template>
src/components/search-option/FYOptionTopTask.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,84 @@
<template>
  <el-form-item :label="label" :prop="prop">
    <el-select
      :model-value="formatedValue"
      @update:model-value="handleChange"
      placeholder="总任务"
      style="width: 260px"
    >
      <el-option
        v-for="s in topTasks"
        :key="s.value"
        :label="s.label"
        :value="s.value"
      />
    </el-select>
  </el-form-item>
</template>
<script>
import taskApi from '@/api/fysp/taskApi';
export default {
  props: {
    label: {
      type: String,
      default: '总任务'
    },
    // è¿”回结果
    value: Object,
    // æ˜¯å¦é»˜è®¤è¿”回初始选项
    initValue: {
      type: Boolean,
      default: true
    },
    // form表单绑定属性名
    prop: {
      type: String,
      default: 'topTaskId'
    },
  },
  emits: ['update:value'],
  data() {
    return {
      selected: {},
      topTasks: [],
    };
  },
  computed: {
    formatedValue() {
      return this.value.tguid;
    }
  },
  methods: {
    //获取查询条件
    getOptions() {
      taskApi.getTopTask().then((res) => {
        const list = res.map((r) => {
          return {
            value: r.tguid,
            label: r.name,
            data: r
          };
        });
        this.topTasks = list;
        if (this.initValue) {
          this.handleChange(list[0].value);
        }
      });
    },
    //查询子任务统计信息
    handleChange(value) {
      const task = this.topTasks.find(
        (t) => t.data.tguid == value
      );
      const param = task ? task.data : {}
      this.$emit('update:value', param);
    },
  },
  mounted() {
    this.getOptions();
  }
};
</script>
src/components/table/FYTable.vue
@@ -90,6 +90,10 @@
      type: Number,
      default: 0
    },
    defaultPageSize: {
      type: Number,
      default: 20
    },
    // é¢å¤–的高度,用于计算表格高度
    extraHeight: {
      type: Number,
@@ -102,7 +106,7 @@
      tableData: [],
      total: 0,
      currentPage: 1,
      pageSize: 20,
      pageSize: this.defaultPageSize,
      loading: false,
      fontSize: 'default'
    };
@@ -142,7 +146,7 @@
        if (nValue != oValue) {
          this.tableHeight = this.calcTableHeight();
        }
      },
      }
    }
  },
  computed: {},
src/constants/menu.js
@@ -74,6 +74,11 @@
    name: '中间数据产品',
    children: [
      {
        path: '/fysp/data-product/middle/home/inspectionSummary',
        icon: 'Document',
        name: '月度巡查简报'
      },
      {
        path: '/fysp/data-product/profollow',
        icon: 'Document',
        name: '问题动态跟踪'
src/main.js
@@ -20,9 +20,6 @@
import isSameOrAfter from 'dayjs/plugin/isSameOrAfter';
import isSameOrBefore from 'dayjs/plugin/isSameOrBefore';
// echarts
import * as echarts from 'echarts'
dayjs.extend(isSameOrAfter);
dayjs.extend(isSameOrBefore);
@@ -30,7 +27,6 @@
const app = createApp(App);
app.config.globalProperties.$fm = timeUtil;
app.config.globalProperties.$echarts = echarts
app.config.globalProperties.$message = ElMessage
app.config.globalProperties.$notification = ElNotification
app.config.globalProperties.$messageBox = ElMessageBox
src/router/index copy.js
@@ -181,10 +181,10 @@
      },
      {
        // åŸºç¡€äº§å“-管理
        name: 'ProdManage',
        name: 'ManageBaseProd',
        path: 'fysp/data-product/base/home',
        component: () =>
          import('@/views/fysp/data-product/base-data-product/ProdManage.vue'),
          import('@/views/fysp/data-product/base-data-product/ManageBaseProd.vue'),
        children: [
          {
            // åŸºç¡€äº§å“-场景清单
src/router/index.js
@@ -70,7 +70,7 @@
                  import('@/views/fysp/data-product/ProdSceneReport.vue')
              },
              {
                //场景报告
                //联合执法清单
                name: 'lawenforcelist',
                path: 'lawenforcelist',
                component: () =>
@@ -82,18 +82,18 @@
                children: [
                  {
                    // åŸºç¡€äº§å“-管理
                    name: 'ProdManage',
                    name: 'ManageBaseProd',
                    path: 'home',
                    component: () =>
                      import(
                        '@/views/fysp/data-product/base-data-product/ProdManage.vue'
                        '@/views/fysp/data-product/base-data-product/ManageBaseProd.vue'
                      ),
                    children: [
                      {
                        // åŸºç¡€äº§å“-场景清单
                        path: 'scene',
                        name: 'ProdSceneInfo',
                        meta: { keepAlive: true, key: 'ProdManage' },
                        meta: { keepAlive: false, key: 'ManageBaseProd' },
                        component: () =>
                          import(
                            '@/views/fysp/data-product/base-data-product/ProdSceneInfo.vue'
@@ -103,17 +103,17 @@
                        // åŸºç¡€äº§å“-规范性评估
                        path: 'evaluate',
                        name: 'ProdEvaluationInfo',
                        meta: { keepAlive: true, key: 'ProdManage' },
                        meta: { keepAlive: false, key: 'ManageBaseProd' },
                        component: () =>
                          import(
                            '@/views/fysp/data-product/base-data-product/ProdEvaluationInfo.vue'
                          )
                      },
                      {
                        // åŸºç¡€äº§å“-巡查信息
                        // åŸºç¡€äº§å“-整改清单
                        path: 'inspection',
                        name: 'ProdInspectionInfo',
                        meta: { keepAlive: true, key: 'ProdManage' },
                        meta: { keepAlive: false, key: 'ManageBaseProd' },
                        component: () =>
                          import(
                            '@/views/fysp/data-product/base-data-product/ProdInspectionInfo.vue'
@@ -123,7 +123,7 @@
                        // åŸºç¡€äº§å“-监测数据
                        path: 'monitordata',
                        name: 'ProdMonitorDataInfo',
                        meta: { keepAlive: true, key: 'ProdManage' },
                        meta: { keepAlive: false, key: 'ManageBaseProd' },
                        component: () =>
                          import(
                            '@/views/fysp/data-product/base-data-product/ProdMonitorDataInfo.vue'
@@ -141,6 +141,73 @@
                      )
                  }
                ]
              },
              {
                name: 'dataProdMiddle',
                path: 'middle',
                children: [
                  {
                    // ä¸­é—´äº§å“-管理
                    name: 'ManageMiddleProd',
                    path: 'home',
                    component: () =>
                      import(
                        '@/views/fysp/data-product/middle-data-product/ManageMiddleProd.vue'
                      ),
                    children: [
                      {
                        // ä¸­é—´äº§å“-月度巡查情况汇总
                        path: 'inspectionSummary',
                        name: 'ProdInspectionSummary',
                        meta: { keepAlive: false, key: 'ManageMiddleProd' },
                        component: () =>
                          import(
                            '@/views/fysp/data-product/middle-data-product/ProdInspectionSummary.vue'
                          )
                      },
                      {
                        // ä¸­é—´äº§å“-月度问题汇总
                        path: 'problemTypeSummary',
                        name: 'ProdProblemTypeSummary',
                        meta: { keepAlive: false, key: 'ManageMiddleProd' },
                        component: () =>
                          import(
                            '@/views/fysp/data-product/middle-data-product/ProdProblemTypeSummary.vue'
                          )
                      },
                      {
                        // ä¸­é—´äº§å“-分街镇单场景问题数均值
                        path: 'problemCountSummary',
                        name: 'ProdProblemCountSummary',
                        meta: { keepAlive: false, key: 'ManageMiddleProd' },
                        component: () =>
                          import(
                            '@/views/fysp/data-product/middle-data-product/ProdProblemCountSummary.vue'
                          )
                      },
                      {
                        // ä¸­é—´äº§å“-监测设备汇总
                        path: 'monitorDeviceSummary',
                        name: 'ProdMonitorDeviceSummary',
                        meta: { keepAlive: false, key: 'ManageMiddleProd' },
                        component: () =>
                          import(
                            '@/views/fysp/data-product/middle-data-product/ProdMonitorDeviceSummary.vue'
                          )
                      },
                      {
                        // ä¸­é—´äº§å“-评估情况
                        path: 'evaluationSummary',
                        name: 'ProdEvaluationSummary',
                        meta: { keepAlive: false, key: 'ManageMiddleProd' },
                        component: () =>
                          import(
                            '@/views/fysp/data-product/middle-data-product/ProdEvaluationSummary.vue'
                          )
                      },
                    ]
                  },
                ]
              }
            ]
          },
src/utils/echart-util.js
@@ -1,123 +1,95 @@
// é€’归的获取obj中的prop属性 è§£å†³æœ‰æ—¶éœ€è¦å–val.obj.prop的情况
function getPropValueLoop(obj, prop) {
  if (typeof prop !== 'string') {
    return obj;
  }
  const props = prop.split('.');
  let result = obj;
  props.forEach((item) => {
    result = result[item];
  });
  return result;
}
function getCount(array, element) {
  let count = 0;
  array.forEach((e) => {
    if (e == element) {
      count++;
    }
  });
  return count;
}
export default {
  /** å°†chart图表转化为图片url
   * @param chart: chart图表的实例
   *  */
  chartToImageUrl(chart) {
    const dataURL = chart.getDataURL({
      pixelRatio: 5, // æé«˜å›¾ç‰‡è´¨é‡
      backgroundColor: '#FFFFFF', // è®¾ç½®èƒŒæ™¯é¢œè‰²
      excludeComponents: ['toolbox'], // æŽ’除工具箱组件
      type: 'png' // è¾“出图片类型为PNG
    });
    return dataURL;
  },
  // å±•示 data æ•°ç»„中对象的 prop å±žæ€§çš„饼图, title æ˜¯é¥¼å›¾çš„æ ‡é¢˜
  getPieChartByDataAndProp(data, prop, label) {
    let chartData = [];
    function hasThisName(name) {
      for (let index = 0; index < chartData.length; index++) {
        const element = chartData[index];
        if (element.name === name) {
          return true;
        }
      }
      return false;
    }
    data.map((item) => {
      const name = getPropValueLoop(item, prop);
      if (hasThisName(name)) {
        chartData.map((item) => {
          if (item.name === name) {
            item.value++;
          }
        });
      } else {
        chartData.push({
          name: name,
          value: 1
        });
      }
    });
function pieChartOption() {
    return {
    color: [
      '#5470c6',
      '#91cc75',
      '#fac858',
      '#ee6666',
      '#73c0de',
      '#3ba272',
      '#fc8452',
      '#9a60b4',
      '#ea7ccc',
      '#514a9d',
      '#2ec7c9',
      '#b6a2de'
    ],
      title: {
        text: label,
        left: 'center'
      text: `饼图默认名称`,
      left: 'center' // æ ‡é¢˜å±…中显示
    },
    // æ·»åŠ å·¥å…·æ é…ç½®ï¼ŒåŒ…å«ä¸‹è½½åŠŸèƒ½
    toolbox: {
      show: true,
      feature: {
        saveAsImage: {
          show: true,
          title: '下载图表',
          type: 'png',
          pixelRatio: 2 // æé«˜å›¾ç‰‡æ¸…晰度
        }
      }
      },
      tooltip: {
        trigger: 'item'
      trigger: 'item', // é¥¼å›¾ä½¿ç”¨item触发tooltip
      formatter: '{a} <br/>{b}: {c} ({d}%)' // æ˜¾ç¤ºæ ¼å¼ï¼šåç§°: æ•°é‡ (百分比)
      },
      legend: {
      show: false,
        orient: 'vertical',
        left: 'left'
      left: 'right', // å›¾ä¾‹å±…左垂直排列
      data: ['sample1', 'sample2', 'sample3'] // å›¾ä¾‹æ•°æ®ä¸ºé—®é¢˜ç±»åž‹åç§°
      },
      series: [
        {
          type: 'pie',
          radius: '50%',
          data: chartData,
          emphasis: {
            itemStyle: {
              shadowBlur: 10,
              shadowOffsetX: 0,
              shadowColor: 'rgba(0, 0, 0, 0.5)'
        name: 'sample',
        type: 'pie', // å›¾è¡¨ç±»åž‹æ”¹ä¸ºé¥¼å›¾
        radius: '60%', // é¥¼å›¾åŠå¾„
        center: ['50%', '55%'], // é¥¼å›¾ä¸­å¿ƒä½ç½®
        data: [
          {
            name: 'sample1',
            value: 100
          },
          {
            name: 'sample2',
            value: 200
          },
          {
            name: 'sample3',
            value: 300
            }
        ],
        label: {
          show: true,
          formatter: '{b}: {c} ({d}%)' // æ‰‡åŒºæ ‡ç­¾æ˜¾ç¤ºï¼šåç§°: æ•°é‡ (百分比)
          }
        }
      ]
    };
  },
  // å±•示 data æ•°ç»„中对象的 prop å±žæ€§çš„直方图, title æ˜¯ç›´æ–¹å›¾çš„æ ‡é¢˜
  getBarChartByDataAndProp(data, prop, title) {
    let series = data.map((item) => getPropValueLoop(item, prop));
    const option = {
      title: {
        text: title //设置标题
      },
      xAxis: {
        type: 'category',
        data: Array.from(new Set(series)),
        axisLabel: {
          rotate: 45, // æ—‹è½¬æ ‡ç­¾ï¼Œé¿å…é‡å 
          // æˆ–者
          interval: 0 // æ˜¾ç¤ºæ‰€æœ‰æ ‡ç­¾ï¼Œå¯èƒ½å¯¼è‡´é‡å ï¼Œæ ¹æ®éœ€æ±‚调整
        }
      },
      yAxis: {
        type: 'value'
      },
      series: [
        {
          data: Array.from(new Set(series)).map((item) =>
            getCount(series, item)
          ),
          type: 'bar',
          smooth: true
// é€šè¿‡ ECharts API ä¸‹è½½å›¾ç‰‡çš„函数
function downloadChartImage(chart, fileName) {
  if (!chart) return; // ç¡®ä¿å›¾è¡¨å·²åˆå§‹åŒ–
  // èŽ·å–å›¾è¡¨å›¾ç‰‡æ•°æ®ï¼ˆæ”¯æŒ png/jpeg æ ¼å¼ï¼ŒpixelRatio æŽ§åˆ¶æ¸…晰度)
  const dataURL = chart.getDataURL({
    type: 'png', // å›¾ç‰‡æ ¼å¼
    pixelRatio: 2, // åƒç´ æ¯”,值越大图片越清晰
    backgroundColor: '#fff', // èƒŒæ™¯è‰²ï¼ˆé»˜è®¤é€æ˜Žï¼‰
    excludeComponents: ['toolbox']
  });
  // åˆ›å»ºä¸‹è½½é“¾æŽ¥
  const link = document.createElement('a');
  link.href = dataURL;
  // è®¾ç½®ä¸‹è½½æ–‡ä»¶åï¼ˆå¯æ ¹æ®å®žé™…需求调整)
  link.download = `${fileName}.png`;
  // æ·»åŠ åˆ°æ–‡æ¡£å¹¶è§¦å‘ä¸‹è½½
  document.body.appendChild(link);
  link.click();
  // æ¸…理链接元素
  document.body.removeChild(link);
        }
      ]
    };
    return option;
  }
};
export { pieChartOption, downloadChartImage };
src/utils/excel.js
@@ -1,5 +1,7 @@
import * as XLSX from 'xlsx';
import FileSaver from 'file-saver';
import { ElMessage } from 'element-plus';
function conversionFromTable(elementId, title) {
  // èŽ·å–è¡¨æ ¼å…ƒç´ 
@@ -38,7 +40,7 @@
      `${title}.xlsx` //修改名字
    );
    //这里可以放入你的回调函数
    alert('开始下载');
    ElMessage.success('开始下载');
  } catch (e) {
    if (typeof console !== 'undefined') console.log(e, table_write);
  }
src/views/fysp/data-product/ProdLawEnforceList.vue
@@ -76,15 +76,11 @@
          <el-icon class="is-loading" v-if="row._loading">
            <Loading color="#409eff" />
          </el-icon>
          <div v-else>
            <template v-if="row._problems.length > 0">
              <div v-for="(p, i) in row._problems" :key="p.guid">
                <el-text>{{ i + 1 }}、</el-text>
                <el-text>{{ p.problemname }}</el-text>
              </div>
          <template v-else-if="row._problems.length > 0">
            <template v-for="(p, i) in row._problems" :key="p.guid">
              <br v-if="i > 0" />{{ i + 1 + '、' + p.problemname }}
            </template>
            <el-text v-else>/</el-text>
          </div>
          </template>
        </template>
      </el-table-column>
      <!-- <el-table-column
@@ -325,7 +321,7 @@
    filterHandler(value, row, column) {
      const property = column['property'];
      return row[property] === value;
    },
    }
  },
  mounted() {
    this.addRefreshEvent(this.$refs.tableRef.doLayout);
src/views/fysp/data-product/base-data-product/ManageBaseProd.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
<template>
  <CompProdManage :menu="menu" />
</template>
<script setup>
import { ref } from 'vue';
import CompProdManage from '@/views/fysp/data-product/components/CompProdManage.vue';
const menu = ref([
  {
    name: '场景清单',
    path: 'scene',
  },
  {
    name: '评估清单',
    path: 'evaluate'
  },
  {
    name: '整改清单',
    path: 'inspection'
  },
  {
    name: '监测数据',
    path: 'monitorData'
  }
]);
</script>
<style scoped></style>
src/views/fysp/data-product/base-data-product/ProdEvaluationInfo.vue
@@ -56,7 +56,7 @@
<script setup>
import { ref, inject } from 'vue';
import dayjs from 'dayjs';
import BaseProdProcess from '@/views/fysp/data-product/base-data-product/components/BaseProdProcess.vue';
import BaseProdProcess from '@/views/fysp/data-product/components/BaseProdProcess.vue';
import dataprodbaseApi from '@/api/fysp/dataprodbaseApi.js';
import { conversionFromTable } from '@/utils/excel';
import { useProdStepChange } from '@/views/fysp/data-product/prod-step-change.js';
src/views/fysp/data-product/base-data-product/ProdInspectionInfo.vue
@@ -37,24 +37,35 @@
        <el-table-column prop="cityname" label="市" width="90" />
        <el-table-column prop="districtname" label="区县" width="90" /> -->
        <el-table-column prop="subTask.townname" label="街道" width="80" />
        <el-table-column prop="problems.length" label="问题数" width="60" />
        <el-table-column label="问题摘要" width="300">
          <template #default="{ row }">
            <template v-for="(value, index) in row.problems" :key="value.guid">
              <br v-if="index > 0" />{{ index + 1 + '、' + value.problemname }}
            </template>
          </template>
        </el-table-column>
        <el-table-column
          prop="problems.length"
          label="问题数"
          width="60"
        />
        <el-table-column prop="scoreLevel" label="问题摘要" width="70" />
        <el-table-column
          prop="evaluate.resultscorebef"
          prop="unChangeProblems.length"
          label="未整改数"
          width="60"
        />
        <el-table-column prop="scoreLevel" label="未整改问题" width="70" />
        <el-table-column
        <el-table-column label="未整改问题" width="300">
          <template #default="{ row }">
            <template
              v-for="(value, index) in row.unChangeProblems"
              :key="value.guid"
            >
              <br v-if="index > 0" />{{ index + 1 + '、' + value.problemname }}
            </template>
          </template>
        </el-table-column>
        <!-- <el-table-column
          prop="evaluate.updatedate"
          label="更新时间"
          width="140"
          :formatter="timeFormat"
        />
        /> -->
      </el-table>
    </template>
  </BaseProdProcess>
@@ -62,7 +73,7 @@
<script setup>
import { ref, inject } from 'vue';
import dayjs from 'dayjs';
import BaseProdProcess from '@/views/fysp/data-product/base-data-product/components/BaseProdProcess.vue';
import BaseProdProcess from '@/views/fysp/data-product/components/BaseProdProcess.vue';
import dataprodbaseApi from '@/api/fysp/dataprodbaseApi.js';
import { conversionFromTable } from '@/utils/excel';
import { useProdStepChange } from '@/views/fysp/data-product/prod-step-change.js';
@@ -77,7 +88,12 @@
    .fetchProdInspectionInfo(opt)
    .then((res) => {
      if (res.success) {
        tableData.value = res.data;
        tableData.value = res.data.map((item) => {
          return {
            ...item,
            unChangeProblems: item.problems.filter((p) => !p.ischanged)
          };
        });
      }
      changeActive();
    })
src/views/fysp/data-product/base-data-product/ProdMonitorDataInfo.vue
@@ -1,4 +1,98 @@
<template>
  1
  <BaseProdProcess
    v-model:active="active"
    @onStep1="onStep1"
    @onStep2="onStep2"
    @onStep3="onStep3"
    :loading="loading"
  >
    <template #step2="{ contentHeight }">
      <el-table
        id="prod-scene-table"
        :data="tableData"
        v-loading="loading"
        :height="contentHeight + 'px'"
        table-layout="fixed"
        :show-overflow-tooltip="true"
        size="small"
        border
      >
        <el-table-column fixed="left" prop="index" label="排名" width="50">
        </el-table-column>
        <el-table-column
          fixed="left"
          prop="scene.name"
          label="名称"
          :show-overflow-tooltip="true"
          min-width="200"
        >
        </el-table-column>
        <el-table-column prop="data.drAvg" label="平均值(mg/m³)" width="100" />
        <el-table-column
          prop="data.drOverAvgPer"
          label="超区均值"
          width="80"
          :formatter="ratioFormat"
        />
        <el-table-column label="超市均值" width="80" />
        <!-- <el-table-column prop="provincename" label="省" width="90" />
        <el-table-column prop="cityname" label="市" width="90" />
        <el-table-column prop="districtname" label="区县" width="90" /> -->
        <el-table-column prop="scene.townname" label="属地" width="110" />
      </el-table>
</template>
<script setup></script>
    <!-- <template #step3></template> -->
  </BaseProdProcess>
</template>
<script setup>
import { ref, inject } from 'vue';
import dayjs from 'dayjs';
import BaseProdProcess from '@/views/fysp/data-product/components/BaseProdProcess.vue';
import dataprodbaseApi from '@/api/fysp/dataprodbaseApi.js';
import { conversionFromTable } from '@/utils/excel';
import { useProdStepChange } from '@/views/fysp/data-product/prod-step-change.js';
const { active, changeActive } = useProdStepChange();
const loading = ref(false);
const tableData = ref([]);
function onStep1(opt) {
  loading.value = true;
  dataprodbaseApi
    .fetchProdMonitorDataInfo(opt)
    .then((res) => {
      if (res.success) {
        tableData.value = res.data
          .sort((a, b) => {
            return b.data.drAvg - a.data.drAvg;
          })
          .map((item, index) => {
            return {
              ...item,
              index: index + 1
            };
          });
      }
      changeActive();
    })
    .finally(() => {
      loading.value = false;
    });
}
function onStep2() {
  changeActive();
}
function onStep3(val) {
  if (val.downloadType == '1') {
    loading.value = true;
    conversionFromTable('prod-scene-table', '在线监测数据清单');
    loading.value = false;
  }
}
function ratioFormat(row, column, cellValue, index) {
  return Math.round(cellValue * 1000) / 10 + '%';
}
</script>
src/views/fysp/data-product/base-data-product/ProdSceneInfo.vue
@@ -6,9 +6,6 @@
    @onStep3="onStep3"
    :loading="loading"
  >
    <!-- <template #step1>
      <ProdQueryOpt :loading="loading" @submit="onSearch"> </ProdQueryOpt>
    </template> -->
    <template #step2="{ contentHeight }">
      <el-table
        id="prod-scene-table"
@@ -50,14 +47,12 @@
        /> -->
      </el-table>
    </template>
    <!-- <template #step3></template> -->
  </BaseProdProcess>
</template>
<script setup>
import { ref, inject } from 'vue';
import dayjs from 'dayjs';
import BaseProdProcess from '@/views/fysp/data-product/base-data-product/components/BaseProdProcess.vue';
import ProdQueryOpt from '@/views/fysp/data-product/base-data-product/components/ProdQueryOpt.vue';
import BaseProdProcess from '@/views/fysp/data-product/components/BaseProdProcess.vue';
import dataprodbaseApi from '@/api/fysp/dataprodbaseApi.js';
import { conversionFromTable } from '@/utils/excel';
import { useProdStepChange } from '@/views/fysp/data-product/prod-step-change.js';
src/views/fysp/data-product/components/BaseProdProcess.vue
ÎļþÃû´Ó src/views/fysp/data-product/base-data-product/components/BaseProdProcess.vue ÐÞ¸Ä
@@ -111,8 +111,8 @@
<script setup>
import { computed, inject, ref, watch, onMounted, onUnmounted } from 'vue';
import { unCalc } from '@/utils/css-util';
import ProdQueryOpt from '@/views/fysp/data-product/base-data-product/components/ProdQueryOpt.vue';
import ProdDownload from '@/views/fysp/data-product/base-data-product/components/ProdDownload.vue';
import ProdQueryOpt from '@/views/fysp/data-product/components/ProdQueryOpt.vue';
import ProdDownload from '@/views/fysp/data-product/components/ProdDownload.vue';
const props = defineProps({
  active: {
src/views/fysp/data-product/components/CompProdManage.vue
ÎļþÃû´Ó src/views/fysp/data-product/base-data-product/ProdManage.vue ÐÞ¸Ä
@@ -2,7 +2,7 @@
  <el-affix>
    <el-menu
      ref="menuRef"
      default-active="scene"
      :default-active="menu.length > 0 ? menu[0].path : ''"
      ellipsis
      mode="horizontal"
    >
@@ -28,35 +28,41 @@
</template>
<script setup>
import { ref, onMounted, provide, inject, computed } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import { useRouter } from 'vue-router';
const contentMaxHeight = inject('contentMaxHeight');
const props = defineProps({
  menu: {
    type: Array,
    default: () => []
  }
})
const router = useRouter();
const route = useRoute();
const menuRef = ref(null);
const height = ref(contentMaxHeight.value);
const menu = ref([
  {
    name: '场景清单',
    path: 'scene',
    selected: true
  },
  {
    name: '评估清单',
    path: 'evaluate'
  },
  {
    name: '整改清单',
    path: 'inspection'
  },
  {
    name: '监测数据',
    path: 'monitorData'
  }
]);
// const menu = ref([
//   {
//     name: '场景清单',
//     path: 'scene',
//     selected: true
//   },
//   {
//     name: '评估清单',
//     path: 'evaluate'
//   },
//   {
//     name: '整改清单',
//     path: 'inspection'
//   },
//   {
//     name: '监测数据',
//     path: 'monitorData'
//   }
// ]);
const navPage = (item) => {
  if (item.index) {
src/views/fysp/data-product/components/CompProdTextGenerator.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
<template>
  <el-input v-model="textarea" autosize type="textarea" placeholder="" />
</template>
<script setup>
import { computed, ref } from 'vue';
const props = defineProps({
  // æ¨¡æ¿
  template: {
    type: String,
    default: ''
  },
  // å‚æ•°
  params: {
    type: Object,
    default: () => {}
  }
});
const textarea = computed(() => {
  return props.template.replace(/{(\w+)}/g, (match, p1) => {
    return props.params[p1] || '';
  });
});
</script>
src/views/fysp/data-product/components/ProdDownload.vue
src/views/fysp/data-product/components/ProdQueryOpt.vue
src/views/fysp/data-product/components/ProdQueryOptCompare.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,102 @@
<template>
  <el-card shadow="never">
    <template #header>
      <div><el-text tag="b" size="large">产品生成选项</el-text></div>
    </template>
    <el-form :inline="true" :model="formSearch">
      <FYOptionTopTask v-model:value="formSearch.topTask"></FYOptionTopTask>
      <FYOptionScene
        :allOption="false"
        :type="2"
        v-model:value="formSearch.scenetype"
      ></FYOptionScene>
    </el-form>
    <el-form :inline="true" :model="formSearch2">
      <FYOptionTopTask v-model:value="formSearch2.topTask"></FYOptionTopTask>
    </el-form>
    <template #footer>
      <el-row v-show="active" justify="end">
        <el-button
          type="primary"
          size="default"
          :loading="loading"
          @click="submit"
          >生成</el-button
        >
      </el-row>
    </template>
  </el-card>
</template>
<script setup>
import { ref, computed } from 'vue';
import dayjs from 'dayjs';
const props = defineProps({
  loading: {
    type: Boolean,
    default: false
  },
  active: {
    type: Boolean,
    default: true
  }
});
const emit = defineEmits(['submit']);
const formSearch = ref({
  topTask: {},
  scenetype: {}
});
const formSearch2 = ref({
  topTask: {}
});
const submit = () => {
  const opt1 = {
    topTaskId: formSearch.value.topTask.tguid,
    topTaskName: formSearch.value.topTask.name,
    provinceCode: formSearch.value.topTask.provincecode,
    provinceName: formSearch.value.topTask.provincename,
    cityCode: formSearch.value.topTask.citycode,
    cityName: formSearch.value.topTask.cityname,
    districtCode: formSearch.value.topTask.districtcode,
    districtName: formSearch.value.topTask.districtname,
    townCode: formSearch.value.topTask.towncode,
    townName: formSearch.value.topTask.townname,
    startTime: dayjs(formSearch.value.topTask.starttime).format(
      'YYYY-MM-DD HH:mm:ss'
    ),
    endTime: dayjs(formSearch.value.topTask.endtime)
      .add(1, 'day')
      .add(-1, 'second')
      .format('YYYY-MM-DD HH:mm:ss'),
    sceneTypeId: formSearch.value.scenetype.value,
    sceneTypeName: formSearch.value.scenetype.label,
    needCache: true
  };
  const opt2 = {
    topTaskId: formSearch2.value.topTask.tguid,
    topTaskName: formSearch2.value.topTask.name,
    provinceCode: formSearch2.value.topTask.provincecode,
    provinceName: formSearch2.value.topTask.provincename,
    cityCode: formSearch2.value.topTask.citycode,
    cityName: formSearch2.value.topTask.cityname,
    districtCode: formSearch2.value.topTask.districtcode,
    districtName: formSearch2.value.topTask.districtname,
    townCode: formSearch2.value.topTask.towncode,
    townName: formSearch2.value.topTask.townname,
    startTime: dayjs(formSearch2.value.topTask.starttime).format(
      'YYYY-MM-DD HH:mm:ss'
    ),
    endTime: dayjs(formSearch2.value.topTask.endtime)
      .add(1, 'day')
      .add(-1, 'second')
      .format('YYYY-MM-DD HH:mm:ss'),
    sceneTypeId: formSearch.value.scenetype.value,
    sceneTypeName: formSearch.value.scenetype.label,
    needCache: true
  };
  emit('submit', [opt1, opt2]);
};
</script>
src/views/fysp/data-product/middle-data-product/ManageMiddleProd.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,31 @@
<template>
  <CompProdManage :menu="menu" />
</template>
<script setup>
import { ref } from 'vue';
import CompProdManage from '@/views/fysp/data-product/components/CompProdManage.vue';
const menu = ref([
  {
    name: '月度巡查情况汇总',
    path: 'inspectionSummary',
  },
  {
    name: '月度问题汇总',
    path: 'problemTypeSummary'
  },
  {
    name: '分街镇单场景问题数均值',
    path: 'problemCountSummary'
  },
  {
    name: '监测设备汇总',
    path: 'monitorDeviceSummary'
  },
  {
    name: '评估情况汇总',
    path: 'evaluationSummary'
  }
]);
</script>
<style scoped></style>
src/views/fysp/data-product/middle-data-product/ProdEvaluationSummary.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
<template>
  <BaseProdProcess
    v-model:active="active"
    @onStep1="onStep1"
    @onStep2="onStep2"
    @onStep3="onStep3"
    :loading="loading"
  >
  </BaseProdProcess>
</template>
<script setup>
import { ref } from 'vue';
import BaseProdProcess from '@/views/fysp/data-product/components/BaseProdProcess.vue';
import dataprodmiddleApi from '@/api/fysp/dataprodmiddleApi.js';
import { conversionFromTable } from '@/utils/excel';
import { useProdStepChange } from '@/views/fysp/data-product/prod-step-change.js';
const { active, changeActive } = useProdStepChange();
const loading = ref(false);
function onStep1(opt) {}
function onStep2() {
  changeActive();
}
function onStep3(val) {}
</script>
src/views/fysp/data-product/middle-data-product/ProdInspectionSummary.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,75 @@
<template>
  <BaseProdProcess
    v-model:active="active"
    @onStep1="onStep1"
    @onStep2="onStep2"
    @onStep3="onStep3"
    :loading="loading"
  >
    <template #step2="{ contentHeight }">
      <el-scrollbar :height="contentHeight">
        <el-card shadow="never">
          <el-form :inline="false" label-position="left" label-width="150px">
            <el-form-item label="巡查场景总数">
              <el-text>{{ data.sceneCount }}</el-text>
            </el-form-item>
            <el-form-item label="巡查点次">
              <el-text>{{ data.pointCount }}</el-text>
            </el-form-item>
            <el-form-item label="复核点次">
              <el-text>{{ data.reviewPointCount }}</el-text>
            </el-form-item>
            <el-form-item label="停工场景数">
              <el-text>{{ data.stopSceneCount }}</el-text>
            </el-form-item>
            <el-form-item label="完工场景数">
              <el-text>{{ data.completeSceneCount }}</el-text>
            </el-form-item>
          </el-form>
          <!-- <CompProdTextGenerator
            :template="template"
            :params="params"
          /> -->
        </el-card>
      </el-scrollbar>
    </template>
  </BaseProdProcess>
</template>
<script setup>
import { ref } from 'vue';
import BaseProdProcess from '@/views/fysp/data-product/components/BaseProdProcess.vue';
import CompProdTextGenerator from '@/views/fysp/data-product/components/CompProdTextGenerator.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 { ElMessage } from 'element-plus';
const { active, changeActive } = useProdStepChange();
const loading = ref(false);
const data = ref({});
const template = ref('2025å¹´7月,第三方在区生态环境局指导下对辖区内72家工地开展了扬尘污染防治现场巡查。对问题较严重或扬尘管控不到位工地进行4家次复核,本轮共计巡查76家次;7月配合联合执法或新工地交底宣贯共出动6组7人次。');
const params = ref({});
function onStep1(opt) {
  loading.value = true;
  dataprodmiddleApi
    .fetchInspectionSummary(opt)
    .then((res) => {
      if (res.success) {
        data.value = res.data;
      }
      changeActive();
    })
    .finally(() => {
      loading.value = false;
    });
}
function onStep2() {
  // changeActive();
  ElMessage.error('暂不提供下载');
}
function onStep3(val) {}
</script>
src/views/fysp/data-product/middle-data-product/ProdMonitorDeviceSummary.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
<template>
  <BaseProdProcess
    v-model:active="active"
    @onStep1="onStep1"
    @onStep2="onStep2"
    @onStep3="onStep3"
    :loading="loading"
  >
  </BaseProdProcess>
</template>
<script setup>
import { ref } from 'vue';
import BaseProdProcess from '@/views/fysp/data-product/components/BaseProdProcess.vue';
import dataprodmiddleApi from '@/api/fysp/dataprodmiddleApi.js';
import { conversionFromTable } from '@/utils/excel';
import { useProdStepChange } from '@/views/fysp/data-product/prod-step-change.js';
const { active, changeActive } = useProdStepChange();
const loading = ref(false);
function onStep1(opt) {}
function onStep2() {
  changeActive();
}
function onStep3(val) {}
</script>
src/views/fysp/data-product/middle-data-product/ProdProblemCountSummary.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,33 @@
<template>
  <BaseProdProcess
    v-model:active="active"
    @onStep2="onStep2"
    @onStep3="onStep3"
    :loading="loading"
  >
    <template #step1>
      <ProdQueryOptCompare @submit="onStep1"></ProdQueryOptCompare>
    </template>
  </BaseProdProcess>
</template>
<script setup>
import { ref } from 'vue';
import BaseProdProcess from '@/views/fysp/data-product/components/BaseProdProcess.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 ProdQueryOptCompare from '@/views/fysp/data-product/components/ProdQueryOptCompare.vue';
const { active, changeActive } = useProdStepChange();
const loading = ref(false);
function onStep1(opts) {
  console.log('onStep1', opts);
}
function onStep2() {
  changeActive();
}
function onStep3(val) {}
</script>
src/views/fysp/data-product/middle-data-product/ProdProblemTypeSummary.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,127 @@
<template>
  <BaseProdProcess
    v-model:active="active"
    @onStep1="onStep1"
    @onStep2="onStep2"
    @onStep3="onStep3"
    :loading="loading"
  >
    <template #step2="{ contentHeight }">
      <el-scrollbar :height="contentHeight">
        <el-row>
          <el-col :span="24">
            <el-table
              id="prod-problem-type-table"
              :data="tableData"
              v-loading="loading"
              table-layout="fixed"
              :show-overflow-tooltip="true"
              size="small"
              border
            >
              <el-table-column
                fixed="left"
                type="index"
                label="编号"
                width="50"
              >
              </el-table-column>
              <el-table-column
                fixed="left"
                prop="typeName"
                label="问题类型"
                min-width="200"
              >
              </el-table-column>
              <el-table-column prop="count" label="数量" min-width="50" />
              <el-table-column
                prop="ratio"
                label="本月占比"
                min-width="70"
                :formatter="ratioFormat"
              />
              <el-table-column
                prop="ratioDiff"
                label="较上月占比变化"
                min-width="70"
                :formatter="ratioFormat"
              />
            </el-table>
          </el-col>
          <el-col :span="24">
            <el-row justify="center">
              <div ref="chartRef" style="height: 400px; width: 100%;max-width: 800px;"></div>
            </el-row>
          </el-col>
        </el-row>
      </el-scrollbar>
    </template>
  </BaseProdProcess>
</template>
<script setup>
import { ref } from 'vue';
import * as echarts from 'echarts';
import BaseProdProcess from '@/views/fysp/data-product/components/BaseProdProcess.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 dayjs from 'dayjs';
import { pieChartOption, downloadChartImage } from '@/utils/echart-util.js';
const { active, changeActive } = useProdStepChange();
const loading = ref(false);
const tableData = ref([]);
const chartRef = ref(null);
let chart;
function onStep1(opt) {
  loading.value = true;
  dataprodmiddleApi
    .fetchProblemTypeSummary(opt)
    .then((res) => {
      if (res.success) {
        tableData.value = res.data;
      }
      changeActive();
      setTimeout(() => {
        genChart(opt);
      }, 500);
    })
    .finally(() => {
      loading.value = false;
    });
}
function onStep2() {
  changeActive();
}
function onStep3(val) {
  if (val.downloadType == '1') {
    loading.value = true;
    // conversionFromTable('prod-problem-type-table', '扬尘污染问题类型占比清单');
    downloadChartImage(chart, '扬尘污染问题类型占比');
    loading.value = false;
  }
}
function genChart(opt) {
  if (chart == undefined) {
    chart = echarts.init(chartRef.value);
  }
  const startTime = dayjs(opt.startTime).format('YYYYå¹´MM月');
  const option = pieChartOption();
  option.title.text = `${startTime}扬尘污染问题类型占比`;
  option.legend.data = tableData.value.map((item) => item.typeName);
  option.series[0].name = '问题类型';
  option.series[0].data = tableData.value.map((item) => ({
    name: item.typeName,
    value: item.count
  }));
  chart.setOption(option);
}
function ratioFormat(row, column, cellValue, index) {
  return Math.round(cellValue * 1000) / 10 + '%';
}
</script>
src/views/fysp/support/JingAnNightConstruction.vue
@@ -4,9 +4,19 @@
    :total-count="total"
    @search="onSearch"
    :extraHeight="tabsHeaderHeight"
    :defaultPageSize="10"
  >
    <!-- <template #options> </template>
    <template #buttons> </template> -->
    <template #options>
      <FYOptionLocation
        :disabled="true"
        :checkStrictly="false"
        :initValue="false"
        :allOption="false"
        :level="3"
        v-model:value="formSearch._locations"
      ></FYOptionLocation>
    </template>
    <!-- <template #buttons> </template> -->
    <template #table-column>
      <el-table-column
        fixed="left"
@@ -97,6 +107,16 @@
const tabsHeaderHeight = inject('tabsHeaderHeight', 0);
const formSearch = ref({
  _locations: {
    pCode: '31',
    pName: '上海市',
    cCode: '3100',
    cName: '上海市',
    dCode: '310106',
    dName: '静安区'
  }
});
// å¤œé—´æ–½å·¥è®°å½•及总数
const data = ref([]);
const total = ref(0);