修改接口
This commit is contained in:
@@ -171,7 +171,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-header {
|
.dashboard-header {
|
||||||
height: 80px;
|
height: 100px;
|
||||||
background: rgba(12, 20, 38, 0.9);
|
background: rgba(12, 20, 38, 0.9);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -5,14 +5,14 @@
|
|||||||
<!-- 全国牛单价排行榜 -->
|
<!-- 全国牛单价排行榜 -->
|
||||||
<div class="panel price-ranking-panel">
|
<div class="panel price-ranking-panel">
|
||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
<h3>全国牛单价排行榜(元/头)</h3>
|
<h3>全国牛单价排行榜(元/斤)</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="price-table">
|
<div class="price-table">
|
||||||
<div class="price-table-header">
|
<div class="price-table-header">
|
||||||
<div>序号</div>
|
<div>序号</div>
|
||||||
<div>省份</div>
|
<div>省份</div>
|
||||||
<div>品种</div>
|
<div>品种</div>
|
||||||
<div>单价(元/头)</div>
|
<div>单价(元/斤)</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-for="(row, idx) in nationalPriceTableSortedRows" :key="row.id" class="price-table-row">
|
<div v-for="(row, idx) in nationalPriceTableSortedRows" :key="row.id" class="price-table-row">
|
||||||
<div>{{ idx + 1 }}</div>
|
<div>{{ idx + 1 }}</div>
|
||||||
@@ -47,9 +47,9 @@
|
|||||||
autoresize
|
autoresize
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="data-notes">
|
<!-- <div class="data-notes">
|
||||||
<div class="data-source">数据来源:国家统计/行业估算(示例)。该值为全国牛总存栏量基准。</div>
|
<div class="data-source">数据来源:国家统计/行业估算(示例)。该值为全国牛总存栏量基准。</div>
|
||||||
</div>
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -59,6 +59,11 @@
|
|||||||
|
|
||||||
<!-- 中间地图区域 -->
|
<!-- 中间地图区域 -->
|
||||||
<section class="dashboard-center">
|
<section class="dashboard-center">
|
||||||
|
<!-- 自定义无数据提示框 -->
|
||||||
|
<div v-if="showNoDataToast" class="no-data-toast">
|
||||||
|
{{ noDataMessage }}
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 地图容器 -->
|
<!-- 地图容器 -->
|
||||||
<div class="map-container">
|
<div class="map-container">
|
||||||
<Map3D @province-click="handleProvinceClick" />
|
<Map3D @province-click="handleProvinceClick" />
|
||||||
@@ -70,21 +75,21 @@
|
|||||||
<!-- 品种单价排行榜(替换原地区-品种明细) -->
|
<!-- 品种单价排行榜(替换原地区-品种明细) -->
|
||||||
<div class="panel price-ranking-panel">
|
<div class="panel price-ranking-panel">
|
||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
<h3>品种单价排行榜(元/头)</h3>
|
<h3>品种单价排行榜(元/斤)</h3>
|
||||||
<select v-model="rightSelectedBreed" class="breed-selector">
|
<select v-model="rightSelectedBreed" class="breed-selector">
|
||||||
<option v-for="b in rightBreedOptions" :key="b" :value="b">{{ b }}</option>
|
<option v-for="b in rightBreedOptions" :key="b" :value="b">{{ b }}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="species-price-table">
|
<div class="species-price-table">
|
||||||
<div class="species-price-table-header">
|
<div class="species-price-table-header">
|
||||||
<div>序号</div>
|
|
||||||
<div>品种</div>
|
<div>品种</div>
|
||||||
|
<div>省份</div>
|
||||||
<div>地区</div>
|
<div>地区</div>
|
||||||
<div>单价(元/头)</div>
|
<div>单价(元/斤)</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-for="(row, idx) in speciesPriceTableSortedRows" :key="row.id" class="species-price-table-row">
|
<div v-for="(row, idx) in speciesPriceTableSortedRows" :key="row.id" class="species-price-table-row">
|
||||||
<div>{{ idx + 1 }}</div>
|
|
||||||
<div>{{ row.breed }}</div>
|
<div>{{ row.breed }}</div>
|
||||||
|
<div>{{ row.province }}</div>
|
||||||
<div>{{ row.region }}</div>
|
<div>{{ row.region }}</div>
|
||||||
<div>{{ formatPrice(row.price) }}</div>
|
<div>{{ formatPrice(row.price) }}</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -92,24 +97,24 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 全国省份单价排行榜(元/头) -->
|
<!-- 全国省份单价排行榜(元/头) -->
|
||||||
<div class="panel price-ranking-panel">
|
<div class="panel price-ranking-panel">
|
||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
<h3>全国省份单价排行榜(元/头)</h3>
|
<h3>全国省份平均单价排行榜(元/斤)</h3>
|
||||||
|
</div>
|
||||||
|
<div class="echarts-container">
|
||||||
|
<v-chart
|
||||||
|
ref="provincePriceRankingChart"
|
||||||
|
class="province-price-ranking-chart"
|
||||||
|
:option="nationalProvincePriceRankingOption"
|
||||||
|
autoresize
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="echarts-container">
|
|
||||||
<v-chart
|
|
||||||
ref="provincePriceRankingChart"
|
|
||||||
class="province-price-ranking-chart"
|
|
||||||
:option="nationalProvincePriceRankingOption"
|
|
||||||
autoresize
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 全国牛出栏率模块(年度全国出栏总量) -->
|
<!-- 全国牛出栏率模块(年度全国出栏总量) -->
|
||||||
<div class="panel livestock-panel">
|
<div class="panel livestock-panel">
|
||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
<h3>全国牛出栏率</h3>
|
<h3>全国牛出栏量</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="echarts-container">
|
<div class="echarts-container">
|
||||||
<v-chart
|
<v-chart
|
||||||
@@ -128,6 +133,30 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
/* 自定义无数据提示框 */
|
||||||
|
.no-data-toast {
|
||||||
|
position: absolute;
|
||||||
|
top: 15%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background: rgba(0, 20, 40, 0.9);
|
||||||
|
border: 1px solid #00ffff;
|
||||||
|
box-shadow: 0 0 15px rgba(0, 255, 255, 0.4);
|
||||||
|
color: #00ffff;
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-radius: 4px;
|
||||||
|
z-index: 9999;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
pointer-events: none;
|
||||||
|
animation: fadeInOut 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInOut {
|
||||||
|
from { opacity: 0; transform: translate(-50%, -10px); }
|
||||||
|
to { opacity: 1; transform: translate(-50%, 0); }
|
||||||
|
}
|
||||||
|
|
||||||
.dashboard-container {
|
.dashboard-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -177,7 +206,7 @@
|
|||||||
}
|
}
|
||||||
.province-price-ranking-chart {
|
.province-price-ranking-chart {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 200px;
|
height: 500px;
|
||||||
}
|
}
|
||||||
.price-ranking-panel {
|
.price-ranking-panel {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -197,14 +226,13 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
flex: 1; /* 自适应撑满当前面板剩余空间 */
|
height: 250px; /* 设置固定高度 */
|
||||||
min-height: 0;
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
.species-price-table-header,
|
.species-price-table-header,
|
||||||
.species-price-table-row {
|
.species-price-table-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 0.7fr 1.2fr 1.4fr 1.2fr; /* 序号/品种/地区/单价 */
|
grid-template-columns: 1.2fr 1fr 1fr 1.2fr; /* 品种/省份/地区/单价 */
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@@ -256,7 +284,7 @@
|
|||||||
}
|
}
|
||||||
.price-bar-track {
|
.price-bar-track {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 18px;
|
height: 12px; /* 条形更细 */
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
background: transparent; /* 去掉背景色,仅保留条形图颜色 */
|
background: transparent; /* 去掉背景色,仅保留条形图颜色 */
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -386,6 +414,8 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import axios from 'axios'
|
||||||
import Map3D from './Map3D.vue'
|
import Map3D from './Map3D.vue'
|
||||||
import { use } from 'echarts/core'
|
import { use } from 'echarts/core'
|
||||||
import { CanvasRenderer, SVGRenderer } from 'echarts/renderers'
|
import { CanvasRenderer, SVGRenderer } from 'echarts/renderers'
|
||||||
@@ -424,13 +454,50 @@ export default {
|
|||||||
},
|
},
|
||||||
emits: ['navigate-to-warning'],
|
emits: ['navigate-to-warning'],
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
|
const showNoDataToast = ref(false)
|
||||||
|
const noDataMessage = ref('')
|
||||||
|
let toastTimer = null
|
||||||
|
|
||||||
// 处理省份点击事件
|
// 处理省份点击事件
|
||||||
const handleProvinceClick = (provinceName) => {
|
const handleProvinceClick = async (provinceName) => {
|
||||||
emit('navigate-to-warning', provinceName)
|
try {
|
||||||
|
console.log('正在请求省份数据:', provinceName);
|
||||||
|
const res = await axios.get('/api/cattle-data', {
|
||||||
|
params: { province: provinceName }
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('省份数据返回:', res.data);
|
||||||
|
const raw = res.data;
|
||||||
|
// 兼容直接返回数组或 { data: [] } 的结构
|
||||||
|
const list = Array.isArray(raw) ? raw : (Array.isArray(raw?.data) ? raw.data : []);
|
||||||
|
|
||||||
|
if (list && list.length > 0) {
|
||||||
|
console.log('数据存在,准备跳转');
|
||||||
|
emit('navigate-to-warning', provinceName)
|
||||||
|
} else {
|
||||||
|
console.warn('该地区无数据');
|
||||||
|
noDataMessage.value = `该地区暂无数据: ${provinceName}`
|
||||||
|
showNoDataToast.value = true
|
||||||
|
if (toastTimer) clearTimeout(toastTimer)
|
||||||
|
toastTimer = setTimeout(() => {
|
||||||
|
showNoDataToast.value = false
|
||||||
|
}, 3000)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取省份数据出错:', error)
|
||||||
|
noDataMessage.value = `获取数据失败: ${provinceName}`
|
||||||
|
showNoDataToast.value = true
|
||||||
|
if (toastTimer) clearTimeout(toastTimer)
|
||||||
|
toastTimer = setTimeout(() => {
|
||||||
|
showNoDataToast.value = false
|
||||||
|
}, 3000)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handleProvinceClick
|
handleProvinceClick,
|
||||||
|
showNoDataToast,
|
||||||
|
noDataMessage
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@@ -438,7 +505,8 @@ export default {
|
|||||||
// 右侧品种单价排行榜下拉选项与选中值
|
// 右侧品种单价排行榜下拉选项与选中值
|
||||||
rightBreedOptions: ['安格斯','牦牛','黄牛','利木赞牛','鲁西牛','奶牛','肉牛','水牛','西门塔尔牛','夏洛莱牛','杂交牛','牛'],
|
rightBreedOptions: ['安格斯','牦牛','黄牛','利木赞牛','鲁西牛','奶牛','肉牛','水牛','西门塔尔牛','夏洛莱牛','杂交牛','牛'],
|
||||||
rightSelectedBreed: '安格斯',
|
rightSelectedBreed: '安格斯',
|
||||||
// 存栏率视图模式:percent 或 count
|
speciesPriceRows: [], // 品种单价排行(接口)
|
||||||
|
// 存栏率视图模式:percent 或 count
|
||||||
livestockViewMode: 'percent',
|
livestockViewMode: 'percent',
|
||||||
// 全国牛总存栏量基准值
|
// 全国牛总存栏量基准值
|
||||||
livestockBaseline: 100000000,
|
livestockBaseline: 100000000,
|
||||||
@@ -569,19 +637,19 @@ export default {
|
|||||||
textStyle: { color: '#ffffff' },
|
textStyle: { color: '#ffffff' },
|
||||||
formatter: function(params) {
|
formatter: function(params) {
|
||||||
const p = params[0]
|
const p = params[0]
|
||||||
return `${p.axisValue}<br/>${p.marker}${p.seriesName}: ${p.value} 万头`
|
return `${p.axisValue}<br/>${p.marker}${p.seriesName}: ${p.value}`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
grid: { left: '12%', right: '8%', bottom: '12%', top: '18%' },
|
grid: { left: '12%', right: '8%', bottom: '12%', top: '18%' },
|
||||||
xAxis: {
|
xAxis: {
|
||||||
type: 'category',
|
type: 'category',
|
||||||
data: ['2020年','2021年','2022年','2023年','2024年'],
|
data: [],
|
||||||
axisLine: { lineStyle: { color: '#00ffff' } },
|
axisLine: { lineStyle: { color: '#00ffff' } },
|
||||||
axisLabel: { color: '#ffffff', fontSize: 10 }
|
axisLabel: { color: '#ffffff', fontSize: 10 }
|
||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: {
|
||||||
type: 'value',
|
type: 'value',
|
||||||
name: '万头',
|
name: '数量',
|
||||||
nameTextStyle: { color: '#00ffff', fontSize: 11 },
|
nameTextStyle: { color: '#00ffff', fontSize: 11 },
|
||||||
axisLine: { lineStyle: { color: '#00ffff' } },
|
axisLine: { lineStyle: { color: '#00ffff' } },
|
||||||
axisLabel: { color: '#ffffff', fontSize: 10 },
|
axisLabel: { color: '#ffffff', fontSize: 10 },
|
||||||
@@ -593,11 +661,11 @@ export default {
|
|||||||
smooth: true,
|
smooth: true,
|
||||||
symbol: 'circle',
|
symbol: 'circle',
|
||||||
symbolSize: 4,
|
symbolSize: 4,
|
||||||
data: [4565.45, 4707.43, 4839.91, 5023.48, 5098.70],
|
data: [],
|
||||||
lineStyle: { color: '#00CDCD', width: 2 },
|
lineStyle: { color: '#00CDCD', width: 2 },
|
||||||
itemStyle: { color: '#00CDCD' },
|
itemStyle: { color: '#00CDCD' },
|
||||||
areaStyle: { color: 'rgba(0,205,205,0.15)' },
|
areaStyle: { color: 'rgba(0,205,205,0.15)' },
|
||||||
label: { show: true, position: 'top', color: '#cfefff', fontSize: 10, formatter: '{c} 万头' }
|
label: { show: true, position: 'top', color: '#cfefff', fontSize: 10, formatter: '{c}' }
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -612,19 +680,19 @@ export default {
|
|||||||
textStyle: { color: '#ffffff' },
|
textStyle: { color: '#ffffff' },
|
||||||
formatter: function(params) {
|
formatter: function(params) {
|
||||||
const p = params[0]
|
const p = params[0]
|
||||||
return `${p.axisValue}<br/>${p.marker}${p.seriesName}: ${p.value} 万头`
|
return `${p.axisValue}<br/>${p.marker}${p.seriesName}: ${p.value}`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
grid: { left: '12%', right: '8%', bottom: '12%', top: '18%' },
|
grid: { left: '12%', right: '8%', bottom: '12%', top: '18%' },
|
||||||
xAxis: {
|
xAxis: {
|
||||||
type: 'category',
|
type: 'category',
|
||||||
data: ['2020年','2021年','2022年','2023年','2024年'],
|
data: [],
|
||||||
axisLine: { lineStyle: { color: '#00ffff' } },
|
axisLine: { lineStyle: { color: '#00ffff' } },
|
||||||
axisLabel: { color: '#ffffff', fontSize: 10 }
|
axisLabel: { color: '#ffffff', fontSize: 10 }
|
||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: {
|
||||||
type: 'value',
|
type: 'value',
|
||||||
name: '万头',
|
name: '数量',
|
||||||
nameTextStyle: { color: '#00ffff', fontSize: 11 },
|
nameTextStyle: { color: '#00ffff', fontSize: 11 },
|
||||||
axisLine: { lineStyle: { color: '#00ffff' } },
|
axisLine: { lineStyle: { color: '#00ffff' } },
|
||||||
axisLabel: { color: '#ffffff', fontSize: 10 },
|
axisLabel: { color: '#ffffff', fontSize: 10 },
|
||||||
@@ -633,10 +701,10 @@ export default {
|
|||||||
series: [{
|
series: [{
|
||||||
name: '全国牛存栏总量',
|
name: '全国牛存栏总量',
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
data: [ 9562, 9817, 10215, 10508, 10046],
|
data: [],
|
||||||
barWidth: '40%',
|
barWidth: '40%',
|
||||||
itemStyle: { color: '#00CDCD' },
|
itemStyle: { color: '#00CDCD' },
|
||||||
label: { show: true, position: 'top', color: '#cfefff', fontSize: 10, formatter: '{c} 万头' }
|
label: { show: true, position: 'top', color: '#cfefff', fontSize: 10, formatter: '{c}' }
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -657,6 +725,9 @@ export default {
|
|||||||
{ id: 10, province: '河南省', breed: '西门塔尔牛', price: 16800 }
|
{ id: 10, province: '河南省', breed: '西门塔尔牛', price: 16800 }
|
||||||
],
|
],
|
||||||
|
|
||||||
|
// 全国省份平均单价数据源(专用于右侧省份排行图表)
|
||||||
|
nationalProvinceAverageRows: [],
|
||||||
|
|
||||||
// 全国牛单价排行榜配置(单位:元/头)
|
// 全国牛单价排行榜配置(单位:元/头)
|
||||||
nationalPriceRankingOption: {
|
nationalPriceRankingOption: {
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
@@ -882,9 +953,7 @@ export default {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
return String(this.nationalLivestockCount || 0)
|
return String(this.nationalLivestockCount || 0)
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
totalLivestock() {
|
totalLivestock() {
|
||||||
// 以数据源计算总存栏量(万头)
|
// 以数据源计算总存栏量(万头)
|
||||||
return this.livestockSpeciesData.reduce((sum, s) => sum + Math.round(s.count / 10000), 0)
|
return this.livestockSpeciesData.reduce((sum, s) => sum + Math.round(s.count / 10000), 0)
|
||||||
@@ -897,12 +966,14 @@ export default {
|
|||||||
},
|
},
|
||||||
// 品种单价排行榜(右侧)基于地区-品种明细,按价格升序
|
// 品种单价排行榜(右侧)基于地区-品种明细,按价格升序
|
||||||
speciesPriceTableSortedRows() {
|
speciesPriceTableSortedRows() {
|
||||||
let rows = Array.isArray(this.detailRows) ? this.detailRows.map(r => ({
|
let rows = (Array.isArray(this.speciesPriceRows) && this.speciesPriceRows.length > 0)
|
||||||
id: r.id,
|
? this.speciesPriceRows
|
||||||
breed: r.breed,
|
: (Array.isArray(this.detailRows) ? this.detailRows.map(r => ({
|
||||||
region: r.region,
|
id: r.id,
|
||||||
price: r.price
|
breed: r.breed,
|
||||||
})) : []
|
region: r.region,
|
||||||
|
price: r.price
|
||||||
|
})) : [])
|
||||||
// 下拉选择生效:按包含关系过滤(处理“安格斯”匹配“安格斯牛”等)
|
// 下拉选择生效:按包含关系过滤(处理“安格斯”匹配“安格斯牛”等)
|
||||||
if (this.rightSelectedBreed) {
|
if (this.rightSelectedBreed) {
|
||||||
rows = rows.filter(x => String(x.breed || '').includes(this.rightSelectedBreed))
|
rows = rows.filter(x => String(x.breed || '').includes(this.rightSelectedBreed))
|
||||||
@@ -910,12 +981,14 @@ export default {
|
|||||||
rows.sort((a, b) => a.price - b.price)
|
rows.sort((a, b) => a.price - b.price)
|
||||||
return rows
|
return rows
|
||||||
},
|
},
|
||||||
// 全国省份单价排行榜(右侧图表):以 nationalPriceTableRows 为数据源,按单价升序
|
// 全国省份平均单价排行榜(右侧图表):优先使用 nationalProvinceAverageRows;否则回退 nationalPriceTableRows
|
||||||
nationalProvincePriceRankingOption() {
|
nationalProvincePriceRankingOption() {
|
||||||
const rows = Array.isArray(this.nationalPriceTableRows) ? [...this.nationalPriceTableRows] : []
|
const base = (Array.isArray(this.nationalProvinceAverageRows) && this.nationalProvinceAverageRows.length > 0)
|
||||||
rows.sort((a, b) => a.price - b.price)
|
? [...this.nationalProvinceAverageRows]
|
||||||
const yCategories = rows.map(r => r.province)
|
: (Array.isArray(this.nationalPriceTableRows) ? [...this.nationalPriceTableRows] : [])
|
||||||
const prices = rows.map(r => r.price)
|
base.sort((a, b) => a.price - b.price)
|
||||||
|
const yCategories = base.map(r => r.province)
|
||||||
|
const prices = base.map(r => r.price)
|
||||||
return {
|
return {
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
tooltip: {
|
tooltip: {
|
||||||
@@ -927,13 +1000,13 @@ export default {
|
|||||||
textStyle: { color: '#ffffff' },
|
textStyle: { color: '#ffffff' },
|
||||||
formatter: function(params) {
|
formatter: function(params) {
|
||||||
const p = params[0]
|
const p = params[0]
|
||||||
return `${p.axisValue}<br/>${p.seriesName}: ${p.value} 元/头`
|
return `${p.axisValue}<br/>${p.seriesName}: ${p.value} 元/斤`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
grid: { left: '0%', right: '10%', bottom: '10%', top: '10%', containLabel: true },
|
grid: { left: '0%', right: '10%', bottom: '5%', top: '5%', containLabel: true },
|
||||||
xAxis: {
|
xAxis: {
|
||||||
type: 'value',
|
type: 'value',
|
||||||
name: '单价(元/头)',
|
name: '单价(元/斤)',
|
||||||
nameTextStyle: { color: '#00ffff', fontSize: 11 },
|
nameTextStyle: { color: '#00ffff', fontSize: 11 },
|
||||||
axisLine: { lineStyle: { color: '#00ffff' } },
|
axisLine: { lineStyle: { color: '#00ffff' } },
|
||||||
axisLabel: { color: '#ffffff', fontSize: 10 },
|
axisLabel: { color: '#ffffff', fontSize: 10 },
|
||||||
@@ -949,10 +1022,21 @@ export default {
|
|||||||
name: '省份单价',
|
name: '省份单价',
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
data: prices,
|
data: prices,
|
||||||
barWidth: '45%',
|
barWidth: '42%',
|
||||||
|
barCategoryGap: '28%',
|
||||||
itemStyle: { color: '#4e73df' },
|
itemStyle: { color: '#4e73df' },
|
||||||
label: { show: true, position: 'right', color: '#cfefff', fontSize: 10, formatter: '{c} 元/头' }
|
label: { show: true, position: 'right', color: '#cfefff', fontSize: 10, formatter: '{c} 元/斤' }
|
||||||
}]
|
}],
|
||||||
|
dataZoom: [
|
||||||
|
{
|
||||||
|
type: 'inside',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
startValue: 0,
|
||||||
|
endValue: yCategories.length - 1,
|
||||||
|
zoomLock: true,
|
||||||
|
filterMode: 'empty'
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 单价条形图需要的最大值(用于计算宽度百分比)
|
// 单价条形图需要的最大值(用于计算宽度百分比)
|
||||||
@@ -968,6 +1052,16 @@ export default {
|
|||||||
return rows
|
return rows
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
rightSelectedBreed: {
|
||||||
|
handler(breed) {
|
||||||
|
if (breed) {
|
||||||
|
this.fetchSpeciesPriceRanking(breed)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// 格式化价格显示(千分位)
|
// 格式化价格显示(千分位)
|
||||||
formatPrice(value) {
|
formatPrice(value) {
|
||||||
@@ -985,6 +1079,63 @@ export default {
|
|||||||
const percent = 5 + Math.pow(ratio, 0.6) * 95
|
const percent = 5 + Math.pow(ratio, 0.6) * 95
|
||||||
return { width: `${percent.toFixed(2)}%` }
|
return { width: `${percent.toFixed(2)}%` }
|
||||||
},
|
},
|
||||||
|
// 拉取全国牛单价排行榜数据并填充表格/图表(字段:type/省份province/单价price)
|
||||||
|
async fetchNationalPriceRanking() {
|
||||||
|
const url = '/api/cattle-data'
|
||||||
|
try {
|
||||||
|
const res = await fetch(url)
|
||||||
|
const raw = await res.json()
|
||||||
|
const list = Array.isArray(raw) ? raw : (Array.isArray(raw?.data) ? raw.data : [])
|
||||||
|
const rows = list.map((item, idx) => ({
|
||||||
|
id: item.id ?? idx + 1,
|
||||||
|
province: item.province ?? item.provinceName ?? '',
|
||||||
|
breed: item.type ?? item.breed ?? '',
|
||||||
|
price: Number(item.price)
|
||||||
|
})).filter(r => r.province && r.breed && Number.isFinite(r.price))
|
||||||
|
// 更新数据源(驱动左侧表格与右侧省份排行图表)
|
||||||
|
this.nationalPriceTableRows = rows
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('获取全国牛单价排行榜失败:', e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 拉取品种单价排行榜数据
|
||||||
|
async fetchSpeciesPriceRanking(breed) {
|
||||||
|
const url = `/api/cattle-data?type=${breed}`
|
||||||
|
try {
|
||||||
|
const res = await fetch(url)
|
||||||
|
const raw = await res.json()
|
||||||
|
const list = Array.isArray(raw) ? raw : (Array.isArray(raw?.data) ? raw.data : [])
|
||||||
|
const rows = list.map((item, idx) => ({
|
||||||
|
id: item.id ?? idx + 1,
|
||||||
|
province: item.province ?? '',
|
||||||
|
region: item.location ?? '',
|
||||||
|
breed: item.type ?? item.breed ?? '',
|
||||||
|
price: Number(item.price)
|
||||||
|
})).filter(r => r.province && r.region && r.breed && Number.isFinite(r.price))
|
||||||
|
this.speciesPriceRows = rows
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(`获取品种 ${breed} 单价排行失败:`, e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 拉取全国省份平均单价排行榜数据(字段:province/provincePrice)
|
||||||
|
async fetchNationalProvinceAverages() {
|
||||||
|
const url = '/api/cattle-data/provinces'
|
||||||
|
try {
|
||||||
|
const res = await fetch(url)
|
||||||
|
const raw = await res.json()
|
||||||
|
const list = Array.isArray(raw) ? raw : (Array.isArray(raw?.data) ? raw.data : [])
|
||||||
|
const rows = list.map((item, idx) => ({
|
||||||
|
id: item.id ?? idx + 1,
|
||||||
|
province: item.province ?? item.provinceName ?? '',
|
||||||
|
price: Number(item.provincePrice)
|
||||||
|
})).filter(r => r.province && Number.isFinite(r.price))
|
||||||
|
this.nationalProvinceAverageRows = rows
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('获取全国省份平均单价失败:', e)
|
||||||
|
}
|
||||||
|
},
|
||||||
// 构建环形图(占比)
|
// 构建环形图(占比)
|
||||||
buildLivestockPieOption() {
|
buildLivestockPieOption() {
|
||||||
const baseline = this.livestockBaseline
|
const baseline = this.livestockBaseline
|
||||||
@@ -1027,6 +1178,85 @@ export default {
|
|||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// 通过接口更新全国存栏/出栏年度数据(以2023年为例)
|
||||||
|
async fetchNationalInventorySlaughter() {
|
||||||
|
// 通过 Vite 代理避免浏览器 CORS,使用相对路径
|
||||||
|
const url = '/api/cattle-data/national'
|
||||||
|
try {
|
||||||
|
const res = await fetch(url)
|
||||||
|
const raw = await res.json()
|
||||||
|
// 接口可能返回 { code, data: {...} } 或直接返回对象,统一取 payload
|
||||||
|
const payload = raw && typeof raw === 'object' && raw.data ? raw.data : raw
|
||||||
|
|
||||||
|
// 固定横轴为 2023/2024/2025,纵轴填充数值(单位:万头;缺失则为 null)
|
||||||
|
const years = ['2023年', '2024年', '2025年']
|
||||||
|
const readNumber = (obj, key) => {
|
||||||
|
if (!obj) return null
|
||||||
|
const v = obj[key]
|
||||||
|
const n = typeof v === 'string' ? Number(v) : (typeof v === 'number' ? v : null)
|
||||||
|
return Number.isFinite(n) ? n : null
|
||||||
|
}
|
||||||
|
|
||||||
|
const inventoryValues = [
|
||||||
|
readNumber(payload, 'nationalInventory23th') ?? readNumber(payload, 'nationalInventory23'),
|
||||||
|
readNumber(payload, 'nationalInventory24th') ?? readNumber(payload, 'nationalInventory24'),
|
||||||
|
readNumber(payload, 'nationalInventory25th') ?? readNumber(payload, 'nationalInventory25')
|
||||||
|
]
|
||||||
|
const slaughterValues = [
|
||||||
|
readNumber(payload, 'nationalSlaughter23th') ?? readNumber(payload, 'nationalSlaughter23'),
|
||||||
|
readNumber(payload, 'nationalSlaughter24th') ?? readNumber(payload, 'nationalSlaughter24'),
|
||||||
|
readNumber(payload, 'nationalSlaughter25th') ?? readNumber(payload, 'nationalSlaughter25')
|
||||||
|
]
|
||||||
|
|
||||||
|
// 更新“全国牛存栏量”
|
||||||
|
this.nationalLivestockYearOption.xAxis.data = years
|
||||||
|
this.nationalLivestockYearOption.series[0].data = inventoryValues
|
||||||
|
this.nationalLivestockYearOption.yAxis.name = '数量(万头)'
|
||||||
|
this.nationalLivestockYearOption.series[0].label = { ...this.nationalLivestockYearOption.series[0].label, formatter: '{c} 万头' }
|
||||||
|
this.nationalLivestockYearOption.tooltip.formatter = function(params) {
|
||||||
|
const p = params[0]
|
||||||
|
return `${p.axisValue}<br/>${p.marker}${p.seriesName}: ${p.value} 万头`
|
||||||
|
}
|
||||||
|
this.nationalLivestockYearOption = { ...this.nationalLivestockYearOption }
|
||||||
|
|
||||||
|
// 更新“全国牛出栏量”
|
||||||
|
this.nationalSlaughterYearOption.xAxis.data = years
|
||||||
|
this.nationalSlaughterYearOption.series[0].data = slaughterValues
|
||||||
|
this.nationalSlaughterYearOption.yAxis.name = '数量(万头)'
|
||||||
|
this.nationalSlaughterYearOption.series[0].label = { ...this.nationalSlaughterYearOption.series[0].label, formatter: '{c} 万头' }
|
||||||
|
this.nationalSlaughterYearOption.tooltip.formatter = function(params) {
|
||||||
|
const p = params[0]
|
||||||
|
return `${p.axisValue}<br/>${p.marker}${p.seriesName}: ${p.value} 万头`
|
||||||
|
}
|
||||||
|
this.nationalSlaughterYearOption = { ...this.nationalSlaughterYearOption }
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('获取全国存栏/出栏接口数据失败:', e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 更新“全国牛存栏量年度柱状图”指定年份的值(单位:万头)
|
||||||
|
updateLivestockYearValue(year, valueWan) {
|
||||||
|
const yearLabel = `${year}年`
|
||||||
|
const x = this.nationalLivestockYearOption.xAxis.data || []
|
||||||
|
const idx = x.indexOf(yearLabel)
|
||||||
|
if (idx >= 0) {
|
||||||
|
const arr = this.nationalLivestockYearOption.series[0].data || []
|
||||||
|
arr[idx] = Number(valueWan)
|
||||||
|
// 触发响应式更新
|
||||||
|
this.nationalLivestockYearOption = { ...this.nationalLivestockYearOption }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 更新“全国牛出栏量年度折线图”指定年份的值(单位:万头)
|
||||||
|
updateSlaughterYearValue(year, valueWan) {
|
||||||
|
const yearLabel = `${year}年`
|
||||||
|
const x = this.nationalSlaughterYearOption.xAxis.data || []
|
||||||
|
const idx = x.indexOf(yearLabel)
|
||||||
|
if (idx >= 0) {
|
||||||
|
const arr = this.nationalSlaughterYearOption.series[0].data || []
|
||||||
|
arr[idx] = Number(valueWan)
|
||||||
|
// 触发响应式更新
|
||||||
|
this.nationalSlaughterYearOption = { ...this.nationalSlaughterYearOption }
|
||||||
|
}
|
||||||
|
},
|
||||||
// 构建柱状图(数量)
|
// 构建柱状图(数量)
|
||||||
buildLivestockBarOption() {
|
buildLivestockBarOption() {
|
||||||
const species = this.livestockSpeciesData
|
const species = this.livestockSpeciesData
|
||||||
@@ -1221,6 +1451,12 @@ export default {
|
|||||||
this.refreshLivestockOption()
|
this.refreshLivestockOption()
|
||||||
this.refreshSlaughterOption()
|
this.refreshSlaughterOption()
|
||||||
this.validateSlaughterData()
|
this.validateSlaughterData()
|
||||||
|
// 拉取全国存栏/出栏年度数据并更新图表
|
||||||
|
this.fetchNationalInventorySlaughter()
|
||||||
|
// 拉取全国牛单价排行榜数据
|
||||||
|
this.fetchNationalPriceRanking()
|
||||||
|
// 拉取全国省份平均单价排行榜数据
|
||||||
|
this.fetchNationalProvinceAverages()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -10,6 +10,15 @@ export default defineConfig({
|
|||||||
vue(),
|
vue(),
|
||||||
vueDevTools(),
|
vueDevTools(),
|
||||||
],
|
],
|
||||||
|
server: {
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: 'https://ad.yunmainiu.com',
|
||||||
|
changeOrigin: true,
|
||||||
|
secure: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
|
|||||||
Reference in New Issue
Block a user