From 0796eebe3520fafb0ac5d36ee584af81506d7e9c Mon Sep 17 00:00:00 2001
From: riku <risaku@163.com>
Date: 星期六, 20 九月 2025 14:05:52 +0800
Subject: [PATCH] 2025.9.20 数据产品(待完成)

---
 src/views/fysp/data-product/middle-data-product/ProdProblemTypeSummary.vue  |    2 
 src/views/fysp/data-product/components/ProdQueryOptCompare.vue              |   36 ++++-
 src/utils/echart-util.js                                                    |   75 ++++++++++++
 src/components/search-option/FYOptionTopTask.vue                            |   41 +++++-
 src/views/fysp/data-product/middle-data-product/ProdProblemCountSummary.vue |  166 ++++++++++++++++++++++++++
 src/views/fysp/data-product/components/BaseProdProcess.vue                  |    2 
 src/components.d.ts                                                         |    1 
 src/views/fysp/data-product/components/ProdDownload.vue                     |   13 +
 8 files changed, 306 insertions(+), 30 deletions(-)

diff --git a/src/components.d.ts b/src/components.d.ts
index 61329ff..b4c64c6 100644
--- a/src/components.d.ts
+++ b/src/components.d.ts
@@ -15,6 +15,7 @@
     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']
+    ElAlert: typeof import('element-plus/es')['ElAlert']
     ElAside: typeof import('element-plus/es')['ElAside']
     ElAvatar: typeof import('element-plus/es')['ElAvatar']
     ElBadge: typeof import('element-plus/es')['ElBadge']
diff --git a/src/components/search-option/FYOptionTopTask.vue b/src/components/search-option/FYOptionTopTask.vue
index 291094b..a6cdd0c 100644
--- a/src/components/search-option/FYOptionTopTask.vue
+++ b/src/components/search-option/FYOptionTopTask.vue
@@ -3,11 +3,11 @@
     <el-select
       :model-value="formatedValue"
       @update:model-value="handleChange"
-      placeholder="鎬讳换鍔�"
+      :placeholder="label"
       style="width: 260px"
     >
       <el-option
-        v-for="s in topTasks"
+        v-for="s in filtedBeforeTask"
         :key="s.value"
         :label="s.label"
         :value="s.value"
@@ -37,17 +37,44 @@
       type: String,
       default: 'topTaskId'
     },
+    // 閫夐」绛涢�夋潯浠讹紝绛涢�夋煇浠诲姟涔嬪墠鐨勭浉鍚岃鏀垮尯鍒掑唴鐨勪换鍔�
+    beforeTask: {
+      type: Object,
+      default: () => {
+        return {};
+      }
+    }
   },
   emits: ['update:value'],
   data() {
     return {
       selected: {},
-      topTasks: [],
+      topTasks: []
     };
   },
   computed: {
+    // 閫夋嫨妗嗕腑浣跨敤椤跺眰浠诲姟id浣滀负閫夐」鍊�
     formatedValue() {
       return this.value.tguid;
+    },
+    // 鏌愪换鍔′箣鍓嶇殑鐩稿悓琛屾斂鍖哄垝鍐呯殑浠诲姟
+    filtedBeforeTask() {
+      const filteredTasks = this.topTasks.filter((t) => {
+        return (
+          (!this.beforeTask.provincecode ||
+            this.beforeTask.provincecode == t.data.provincecode) &&
+          (!this.beforeTask.citycode ||
+            this.beforeTask.citycode == t.data.citycode) &&
+          (!this.beforeTask.districtcode ||
+            this.beforeTask.districtcode == t.data.districtcode) &&
+          (!this.beforeTask.starttime ||
+            t.data.starttime < this.beforeTask.starttime)
+        );
+      });
+      if (filteredTasks.length > 0) {
+        this.handleChange(filteredTasks[0]?.value);
+      }
+      return filteredTasks;
     }
   },
   methods: {
@@ -69,13 +96,11 @@
     },
     //鏌ヨ瀛愪换鍔$粺璁′俊鎭�
     handleChange(value) {
-      const task = this.topTasks.find(
-        (t) => t.data.tguid == value
-      );
-      const param = task ? task.data : {}
+      const task = this.topTasks.find((t) => t.data.tguid == value);
+      const param = task ? task.data : {};
 
       this.$emit('update:value', param);
-    },
+    }
   },
   mounted() {
     this.getOptions();
diff --git a/src/utils/echart-util.js b/src/utils/echart-util.js
index ad12e65..7f907dd 100644
--- a/src/utils/echart-util.js
+++ b/src/utils/echart-util.js
@@ -69,6 +69,79 @@
   };
 }
 
+function barChartOption() {
+  return {
+    title: {
+      text: `鏌辩姸鍥鹃粯璁ゅ悕绉癭,
+      left: 'center' // 鏍囬灞呬腑鏄剧ず
+    },
+    // 娣诲姞宸ュ叿鏍忛厤缃紝鍖呭惈涓嬭浇鍔熻兘
+    toolbox: {
+      show: true,
+      feature: {
+        saveAsImage: {
+          show: true,
+          title: '涓嬭浇鍥捐〃',
+          type: 'png',
+          pixelRatio: 2 // 鎻愰珮鍥剧墖娓呮櫚搴�
+        }
+      }
+    },
+    tooltip: {
+      trigger: 'axis', // 鏌辩姸鍥句娇鐢╝xis瑙﹀彂tooltip
+      axisPointer: {
+        type: 'shadow' // 鏄剧ず闃村奖鎸囩ず鍣�
+      },
+      formatter: '{b}: {c}' // 鏄剧ず鏍煎紡锛氬悕绉�: 鏁伴噺
+    },
+    legend: {
+      show: true,
+      orient: 'horizontal',
+      bottom: '0%', // 鍥句緥搴曢儴姘村钩鎺掑垪
+    },
+    grid: {
+      // left: '3%',
+      // right: '4%',
+      bottom: '10%',
+      top: '15%',
+      containLabel: true
+    },
+    xAxis: {
+      name: '鍧愭爣杞�',
+      type: 'category',
+      data: ['sample1', 'sample2', 'sample3'], // X杞存暟鎹�
+      axisTick: {
+        alignWithLabel: true
+      },
+      axisLabel: {
+        rotate: 45,
+      }
+    },
+    yAxis: {
+      type: 'value',
+      name: '鏁伴噺', // Y杞村悕绉�
+      axisLine: {
+        show: true
+      },
+      axisLabel: {
+        formatter: '{value}'
+      }
+    },
+    series: [
+      {
+        name: 'sample',
+        type: 'bar', // 鍥捐〃绫诲瀷鏀逛负鏌辩姸鍥�
+        data: [100, 200, 300], // 鏁版嵁鍊�
+        label: {
+          show: true,
+          position: 'top', // 鏍囩鏄剧ず鍦ㄦ煴瀛愰《閮�
+          formatter: '{c}' // 鏍囩鏍煎紡锛氭暟閲�
+        }
+      }
+    ]
+  };
+}
+
 // 閫氳繃 ECharts API 涓嬭浇鍥剧墖鐨勫嚱鏁�
 function downloadChartImage(chart, fileName) {
   if (!chart) return; // 纭繚鍥捐〃宸插垵濮嬪寲
@@ -92,4 +165,4 @@
   document.body.removeChild(link);
 }
 
-export { pieChartOption, downloadChartImage };
+export { pieChartOption, barChartOption, downloadChartImage };
diff --git a/src/views/fysp/data-product/components/BaseProdProcess.vue b/src/views/fysp/data-product/components/BaseProdProcess.vue
index 699d958..cc6d0de 100644
--- a/src/views/fysp/data-product/components/BaseProdProcess.vue
+++ b/src/views/fysp/data-product/components/BaseProdProcess.vue
@@ -8,7 +8,7 @@
       >
         <div v-show="showStep1Content">
           <template v-if="$slots.step1">
-            <slot name="step1"></slot>
+            <slot name="step1" :onSearch="onSearch"></slot>
           </template>
           <template v-else>
             <ProdQueryOpt :loading="loading" @submit="onSearch"> </ProdQueryOpt>
diff --git a/src/views/fysp/data-product/components/ProdDownload.vue b/src/views/fysp/data-product/components/ProdDownload.vue
index 2ee4fa6..8c073f1 100644
--- a/src/views/fysp/data-product/components/ProdDownload.vue
+++ b/src/views/fysp/data-product/components/ProdDownload.vue
@@ -5,13 +5,13 @@
     </template>
     <el-form :inline="false" label-position="left" label-width="150px">
       <el-form-item label="鍖哄幙">
-        <el-text>{{ queryOpt.districtName }}</el-text>
+        <el-text>{{ opts.districtName }}</el-text>
       </el-form-item>
       <el-form-item label="鏃堕棿鑼冨洿">
-        <el-text>{{ queryOpt.startTime }} 鑷� {{ queryOpt.endTime }}</el-text>
+        <el-text>{{ opts.startTime }} 鑷� {{ opts.endTime }}</el-text>
       </el-form-item>
       <el-form-item label="鍦烘櫙绫诲瀷">
-        <el-text>{{ queryOpt.sceneTypeName }}</el-text>
+        <el-text>{{ opts.sceneTypeName }}</el-text>
       </el-form-item>
       <el-form-item label="浜у搧褰㈠紡">
         <el-radio-group v-model="downloadType">
@@ -53,6 +53,13 @@
 const emit = defineEmits(['submit']);
 
 const downloadType = ref('1');
+const opts = computed(() => {
+  if (props.queryOpt instanceof Array && props.queryOpt.length > 0) {
+    return props.queryOpt[0];
+  } else {
+    return props.queryOpt;
+  }
+});
 
 const submit = () => {
   emit('submit', {
diff --git a/src/views/fysp/data-product/components/ProdQueryOptCompare.vue b/src/views/fysp/data-product/components/ProdQueryOptCompare.vue
index 0935f07..6f63038 100644
--- a/src/views/fysp/data-product/components/ProdQueryOptCompare.vue
+++ b/src/views/fysp/data-product/components/ProdQueryOptCompare.vue
@@ -3,17 +3,31 @@
     <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>
+    <el-space fill>
+      <el-alert type="info" show-icon :closable="false">
+        <p>閫夋嫨鏈湡闇�瑕佺粺璁$殑鎬讳换鍔″拰鍦烘櫙绫诲瀷</p>
+      </el-alert>
+      <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-space>
+
+    <el-space fill>
+      <el-alert type="info" show-icon :closable="false">
+        <p>閫夋嫨闇�瑕佽繘琛屽姣旂殑鍘嗗彶鐗堟湰</p>
+      </el-alert>
+      <el-form :inline="true" :model="formSearch2">
+        <FYOptionTopTask
+          :beforeTask="formSearch.topTask"
+          v-model:value="formSearch2.topTask"
+        ></FYOptionTopTask>
+      </el-form>
+    </el-space>
     <template #footer>
       <el-row v-show="active" justify="end">
         <el-button
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 fe5212f..83aac7e 100644
--- a/src/views/fysp/data-product/middle-data-product/ProdProblemCountSummary.vue
+++ b/src/views/fysp/data-product/middle-data-product/ProdProblemCountSummary.vue
@@ -1,33 +1,189 @@
 <template>
   <BaseProdProcess
     v-model:active="active"
+    @onStep1="onStep1"
     @onStep2="onStep2"
     @onStep3="onStep3"
     :loading="loading"
   >
-    <template #step1>
-      <ProdQueryOptCompare @submit="onStep1"></ProdQueryOptCompare>
+    <template #step1="{ onSearch }">
+      <ProdQueryOptCompare :loading="loading" @submit="onSearch"></ProdQueryOptCompare>
+    </template>
+    <template #step2="{ contentHeight }">
+      <el-scrollbar :height="contentHeight">
+        <el-table
+          id="prod-problem-count-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="districtName"
+            label="鍖哄幙"
+            width="110"
+          >
+          </el-table-column>
+          <el-table-column prop="townName" label="琛楅晣" width="110" />
+          <el-table-column
+            prop="sceneCount"
+            label="鏈湡鍦烘櫙鏁�"
+            min-width="70"
+          />
+          <el-table-column
+            prop="lastSceneCount"
+            label="涓婃湡鍦烘櫙鏁�"
+            min-width="70"
+          />
+          <el-table-column
+            prop="problemCount"
+            label="鏈湡闂鏁�"
+            min-width="70"
+          />
+          <el-table-column
+            prop="lastProblemCount"
+            label="涓婃湡闂鏁�"
+            min-width="70"
+          />
+          <el-table-column
+            prop="ratio"
+            label="鏈湡闂鏁板潎鍊�"
+            min-width="70"
+            :formatter="ratioFormat"
+          />
+          <el-table-column
+            prop="lastRatio"
+            label="涓婃湡闂鏁板潎鍊�"
+            min-width="70"
+            :formatter="ratioFormat"
+          />
+        </el-table>
+        <el-row justify="center">
+          <div
+            ref="chartRef"
+            style="height: 400px; width: 100%; max-width: 800px"
+          ></div>
+        </el-row>
+      </el-scrollbar>
     </template>
   </BaseProdProcess>
 </template>
 <script setup>
-import { ref } from 'vue';
+import { computed, ref } from 'vue';
+import * as echarts from 'echarts';
+import dayjs from 'dayjs';
 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 { barChartOption, downloadChartImage } from '@/utils/echart-util.js';
 import ProdQueryOptCompare from '@/views/fysp/data-product/components/ProdQueryOptCompare.vue';
 
 const { active, changeActive } = useProdStepChange();
 const loading = ref(false);
+const tableData1 = ref([]);
+const tableData2 = ref([]);
+const chartRef = ref(null);
+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(item.ratio * 10) / 10 || 0
+    };
+  });
+});
 
 function onStep1(opts) {
-  console.log('onStep1', opts);
+  loading.value = true;
+  const p1 = dataprodmiddleApi.fetchProblemCountByArea(opts[0]).then((res) => {
+    if (res.success) {
+      tableData1.value = res.data;
+    }
+  });
+  const p2 = dataprodmiddleApi.fetchProblemCountByArea(opts[1]).then((res) => {
+    if (res.success) {
+      tableData2.value = res.data;
+    }
+  });
+  Promise.all([p1, p2])
+    .then(() => {
+      changeActive();
+      setTimeout(() => {
+        genChart(opts[0], opts[1]);
+      }, 500);
+    })
+    .finally(() => {
+      loading.value = false;
+    });
 }
 
 function onStep2() {
   changeActive();
 }
 
-function onStep3(val) {}
+function onStep3(val) {
+  if (val.downloadType == '1') {
+    loading.value = true;
+    conversionFromTable('prod-problem-count-table', '鎵皹姹℃煋闂鏁板潎鍊煎姣�');
+    downloadChartImage(chart, '鎵皹姹℃煋闂鏁板潎鍊煎姣�');
+    loading.value = false;
+  }
+}
+
+function genChart(opt1, opt2) {
+  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.sceneTypeName}鎵皹姹℃煋闂鏁板潎鍊煎姣擿;
+
+  option.xAxis.name = '琛楅亾锛堥晣锛�';
+  option.xAxis.data = tableData.value.map((item) => item.townName);
+  option.yAxis.name = '闂鏁板潎鍊�';
+
+  option.series = [
+    {
+      name: `${month1}鏈坄,
+      type: 'bar', // 鍥捐〃绫诲瀷鏀逛负鏌辩姸鍥�
+      data: tableData1.value.map((item) => item.ratio),
+      label: {
+        show: true,
+        position: 'top', // 鏍囩鏄剧ず鍦ㄦ煴瀛愰《閮�
+        formatter: '{c}' // 鏍囩鏍煎紡锛氭暟閲�
+      }
+    },
+    {
+      name: `${month2}鏈坄,
+      type: 'bar', // 鍥捐〃绫诲瀷鏀逛负鏌辩姸鍥�
+      data: tableData1.value.map((item) => item.ratio),
+      label: {
+        show: true,
+        position: 'top', // 鏍囩鏄剧ず鍦ㄦ煴瀛愰《閮�
+        formatter: '{c}' // 鏍囩鏍煎紡锛氭暟閲�
+      }
+    }
+  ];
+  chart.setOption(option);
+}
+
+function ratioFormat(row, column, cellValue, index) {
+  return Math.round(cellValue * 10) / 10;
+}
 </script>
diff --git a/src/views/fysp/data-product/middle-data-product/ProdProblemTypeSummary.vue b/src/views/fysp/data-product/middle-data-product/ProdProblemTypeSummary.vue
index 181450a..93c161b 100644
--- a/src/views/fysp/data-product/middle-data-product/ProdProblemTypeSummary.vue
+++ b/src/views/fysp/data-product/middle-data-product/ProdProblemTypeSummary.vue
@@ -99,7 +99,7 @@
 function onStep3(val) {
   if (val.downloadType == '1') {
     loading.value = true;
-    // conversionFromTable('prod-problem-type-table', '鎵皹姹℃煋闂绫诲瀷鍗犳瘮娓呭崟');
+    conversionFromTable('prod-problem-type-table', '鎵皹姹℃煋闂绫诲瀷鍗犳瘮娓呭崟');
     downloadChartImage(chart, '鎵皹姹℃煋闂绫诲瀷鍗犳瘮');
     loading.value = false;
   }

--
Gitblit v1.9.3