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