123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262 |
- <template>
- <div class="chart-page">
- <!-- 加载状态 -->
- <div v-if="loading" class="loading-indicator">
- <div class="spinner"></div>
- <p>数据加载中...</p>
- </div>
-
- <!-- 错误提示 -->
- <div v-else-if="error" class="error-message">
- <i class="fa fa-exclamation-circle"></i> {{ error }}
- </div>
-
- <!-- 图表容器 -->
- <div v-else ref="chartContainer" class="chart-container"></div>
- </div>
- </template>
- <!--按河流划分计算的柱状图-->
- <script setup>
- import { ref, reactive, onMounted, onBeforeUnmount } from 'vue'
- import { wgs84togcj02 } from 'coordtransform';
- import * as echarts from 'echarts'
- // 状态管理
- const error = ref(null)
- const loading = ref(true) // 新增加载状态
- const chartContainer = ref(null)
- let chart = null
- const state = reactive({
- excelData: [], // 存储解析后的断面数据
- riverAvgData: [] // 存储按河流分组后的平均数据
- })
- // 从接口获取数据并初始化
- const initData = async () => {
- try {
- // 接口地址
- const apiUrl = 'http://localhost:8000/api/vector/export/all?table_name=cross_section'
-
- // 发起接口请求
- const response = await fetch(apiUrl)
- if (!response.ok) {
- throw new Error(`接口请求失败:HTTP ${response.status}`)
- }
-
- // 解析GeoJSON数据
- const geoJsonData = await response.json()
-
- // 处理每个Feature的properties
- state.excelData = geoJsonData.features
- .map(feature => {
- const props = feature.properties
-
- // 处理经纬度(保持原有坐标转换逻辑)
- const lng = Number(props.longitude)
- const lat = Number(props.latitude)
-
- if (isNaN(lat) || isNaN(lng)) {
- console.error('无效经纬度数据:', props)
- return null
- }
-
- // WGS84转GCJ02坐标
- const [gcjLng, gcjLat] = wgs84togcj02(lng, lat)
-
- return {
- id: props.id,
- river: props.river_name || '未知河流',
- location: props.position || '未知位置',
- district: props.county || '未知区县',
- cdValue: props.cd_concentration !== undefined ? props.cd_concentration : '未知',
- latitude: gcjLat,
- longitude: gcjLng
- }
- })
- .filter(item => item !== null) // 过滤无效数据
-
- // 计算河流平均值
- calculateRiverAvg()
-
- } catch (err) {
- error.value = `数据加载失败:${err.message}`
- console.error('数据处理错误:', err)
- } finally {
- loading.value = false
- }
- }
- // 按河流分组计算平均值
- const calculateRiverAvg = () => {
- const riverGroups = {};
- // 分组统计
- state.excelData.forEach(item => {
- if (!riverGroups[item.river]) {
- riverGroups[item.river] = { total: 0, count: 0 }
- }
- riverGroups[item.river].total += item.cdValue
- riverGroups[item.river].count += 1
- });
- // 计算各组平均值
- const riverAvg = [];
- let totalAll = 0;
- let countAll = 0;
- for (const river in riverGroups) {
- const avg = riverGroups[river].total / riverGroups[river].count;
- riverAvg.push({
- river,
- avg: parseFloat(avg).toFixed(6) //保留6位小数
- });
- totalAll += riverGroups[river].total;
- countAll += riverGroups[river].count;
- }
- // 添加总平均值
- const totalAvg = {
- river: '总河流平均',
- avg: parseFloat((totalAll / countAll)).toFixed(6)
- };
- riverAvg.push(totalAvg);
- state.riverAvgData = riverAvg;
- updateChart(); // 更新图表
- }
- // 初始化ECharts实例
- const initChart = () => {
- if (chartContainer.value) {
- chart = echarts.init(chartContainer.value)
- updateChart()
- }
- }
- // 更新图表数据
- const updateChart = () => {
- if (!chart || state.riverAvgData.length === 0) return;
- // 处理图表数据
- const rivers = state.riverAvgData.map(item => item.river)
- const avgs = state.riverAvgData.map(item => item.avg)
- // ECharts配置项
- const option = {
- tooltip: {
- trigger: 'axis',
- axisPointer: { type: 'shadow' },
- formatter: '{a} <br/>{b}: {c} ug/L'
- },
- grid: {
- right: '4%',
- bottom: '3%',
- containLabel: true
- },
- xAxis: {
- type: 'category',
- data: rivers,
- axisLabel: { interval: 0, fontSize: 15 }
- },
- yAxis: {
- type: 'value',
- name: 'Cd浓度 (ug/L)',
- min: 0,
- nameTextStyle: { fontSize: 15},
- axisLabel: { formatter: '{value}', fontSize: 15 }
- },
- series: [{
- name: '平均镉浓度',
- type: 'bar',
- data: avgs,
- itemStyle: {
- color: (params) =>
- params.dataIndex === rivers.length - 1 ? '#FF4500' : '#1E88E5'
- },
- label: {
- show: true,
- position: 'top',
- formatter: '{c}',
- fontSize: 15
- },
- emphasis: { focus: 'series' }
- }]
- };
- chart.setOption(option)
- }
- // 生命周期钩子
- onMounted(async () => {
- await initData() // 先加载数据
- initChart() // 再初始化图表
-
- // 监听窗口变化
- window.addEventListener('resize', () => {
- if (chart) chart.resize()
- })
- })
- onBeforeUnmount(() => {
- if (chart) chart.dispose() // 销毁图表实例
- })
- </script>
- <style scoped>
- .chart-page {
- width: 100%;
- margin: 0 auto 24px;
- background-color: white;
- border-radius: 12px;
- padding: 20px;
- box-sizing: border-box;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
- }
- .chart-container {
- width: 100%;
- height: 400px;
- margin: 0 auto;
- border-radius: 12px;
- }
- /* 加载状态样式 */
- .loading-indicator {
- text-align: center;
- padding: 40px 0;
- color: #6b7280;
- }
- .spinner {
- width: 40px;
- height: 40px;
- margin: 0 auto 16px;
- border: 4px solid #e5e7eb;
- border-top: 4px solid #3b82f6;
- border-radius: 50%;
- animation: spin 1s linear infinite;
- }
- @keyframes spin {
- 0% { transform: rotate(0deg); }
- 100% { transform: rotate(360deg); }
- }
- /* 错误提示样式 */
- .error-message {
- color: #dc2626;
- background-color: #fee2e2;
- padding: 12px 16px;
- border-radius: 6px;
- margin-bottom: 16px;
- display: flex;
- align-items: center;
- font-weight: 500;
- }
- .error-message i {
- margin-right: 8px;
- font-size: 18px;
- }
- </style>
|