Files
datav---Cattle-Industry/src/components/Home.vue

1529 lines
40 KiB
Vue
Raw Normal View History

<template>
<div class="dashboard-container">
<!-- 左侧面板 -->
<aside class="dashboard-left">
<!-- 不同品种牛出栏率统计模块 -->
<div class="panel slaughter-panel">
<div class="panel-header">
<h3>不同品种牛出栏率统计</h3>
</div>
<div>
<div class="echarts-container">
<v-chart
ref="slaughterPieChart"
class="slaughter-pie"
:option="slaughterPieOption"
autoresize
/>
<v-chart
ref="slaughterBarChart"
class="slaughter-bar"
:option="slaughterBarOption"
autoresize
/>
</div>
<div class="slaughter-legend">
<div class="legend-item" v-for="s in slaughterSpeciesData" :key="s.key">
<span class="legend-color" :style="{ background: s.color }"></span>
<span class="legend-label">{{ s.name }}</span>
</div>
</div>
<div class="data-notes">
<div class="data-source">数据来源行业估算示例区间值展示按区间中值绘图标签显示区间</div>
<div v-if="validationMessageSlaughter" class="validation-message">{{ validationMessageSlaughter }}</div>
</div>
</div>
</div>
<!-- 不同品种牛存栏率模块 -->
<div class="panel livestock-panel">
<div class="panel-header">
<h3>不同品种牛存栏率</h3>
</div>
<div class="echarts-container">
<v-chart
ref="livestockPieChart"
class="livestock-pie"
:option="livestockPieOption"
autoresize
/>
<v-chart
ref="livestockBarChart"
class="livestock-bar"
:option="livestockBarOption"
autoresize
/>
</div>
<div class="livestock-legend">
<div class="legend-item" v-for="s in livestockSpeciesData" :key="s.key">
<span class="legend-color" :style="{ background: s.color }"></span>
<span class="legend-label">{{ s.name }}</span>
</div>
</div>
<div class="data-notes">
<!-- <div class="total-annotation">全国牛总存栏量基准值100,000,000</div> -->
<!-- <div class="yak-note">牦牛全球占比&gt;90%国内占比16%</div> -->
<div class="data-source">数据来源国家统计/行业估算示例各品类统计口径可能不同比例求和不一定为100%</div>
</div>
</div>
<!-- 不同品种年度总销售额柱状图模块 -->
<div class="panel sales-revenue-panel">
<div class="panel-header">
<h3>不同品种年度总销售额</h3>
</div>
<v-chart
ref="salesRevenueChart"
class="sales-revenue-chart"
:option="salesRevenueChartOption"
autoresize
/>
</div>
</aside>
<!-- 中间地图区域 -->
<section class="dashboard-center">
<!-- 地图容器 -->
<div class="map-container">
<Map3D @province-click="handleProvinceClick" />
</div>
</section>
<!-- 右侧面板 -->
<aside class="dashboard-right">
<!-- 地区-品种明细表 -->
<div class="panel detail-panel">
<div class="panel-header">
<h3>地区-品种明细</h3>
</div>
<div class="detail-table">
<div class="detail-header">
<div>地区</div>
<div>存栏()</div>
<div>出售()</div>
<div>单价(/)</div>
</div>
<div v-for="row in filteredDetailRows" :key="row.id" class="detail-row">
<div>{{ row.region }}</div>
<div>{{ row.stock }}</div>
<div>{{ row.sold }}</div>
<div>{{ row.price }}</div>
</div>
</div>
</div>
<!-- 品种单价排行榜 -->
<div class="panel price-panel" >
<div class="panel-header">
<h3>2025年主要品种牛单价趋势预测/公斤活重</h3>
</div>
<div class="price-content">
<v-chart
class="price-chart"
:option="priceRankingOption"
autoresize
/>
</div>
</div>
</aside>
</div>
</template>
<style scoped>
.dashboard-container {
display: flex;
flex-wrap: wrap;
gap: 20px;
padding: 10px;
box-sizing: border-box;
}
.dashboard-center {
flex: 1; /* 中心区域保持基准宽度 */
min-width: 300px;
display: flex;
flex-direction: column;
height: 100vh; /* 确保中间区域占满整个视口高度 */
padding: 10px;
}
.dashboard-left,
.dashboard-right {
flex: 0.6; /* 两侧数据栏进一步缩小至约60%相对权重 */
min-width: 300px; /* 保持最小宽度,避免内容截断 */
max-width: 520px;
}
/* 中低分辨率下进一步缩小到70%,保证协调性 */
@media (max-width: 1366px) {
.dashboard-left,
.dashboard-right {
flex: 0.55;
}
}
@media (max-width: 768px) {
.dashboard-left,
.dashboard-center,
.dashboard-right {
flex: 100%;
}
}
/* 侧边数据栏面板样式,参考预警监测模块 */
.panel {
background: rgba(7, 59, 68, 0.15);
border: 1px solid rgba(0, 212, 255, 0.3);
border-radius: 12px;
padding: 20px;
backdrop-filter: blur(10px);
position: relative;
overflow: hidden;
margin-bottom: 15px;
}
.panel::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
background: #00d4ff;
opacity: 0.6;
}
.panel-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 10px;
}
.panel-header h3 {
color: #ffffff;
font-size: 16px;
font-weight: 700;
margin: 0;
}
.diamond-icon {
width: 12px;
height: 12px;
background: #00d4ff;
transform: rotate(45deg);
box-shadow: 0 0 10px rgba(0, 212, 255, 0.5);
}
/* 统一下拉选择样式 */
.region-select,
.breed-selector {
margin-left: auto;
background: rgba(0, 0, 0, 0.2);
border: 1px solid rgba(0, 212, 255, 0.3);
color: #ffffff;
border-radius: 6px;
padding: 4px 8px;
font-size: 12px;
}
.region-select option,
.breed-selector option {
background: #0c1426;
color: #ffffff;
}
</style>
<script>
import Map3D from './Map3D.vue'
import { use } from 'echarts/core'
import { CanvasRenderer, SVGRenderer } from 'echarts/renderers'
import { PieChart, BarChart } from 'echarts/charts'
import {
TitleComponent,
TooltipComponent,
LegendComponent,
GridComponent
} from 'echarts/components'
import * as echarts from 'echarts'
import VChart from 'vue-echarts'
use([
CanvasRenderer,
SVGRenderer,
PieChart,
BarChart,
TitleComponent,
TooltipComponent,
LegendComponent,
GridComponent
])
export default {
name: 'Home',
components: {
Map3D,
VChart
},
props: {
selectedProvince: {
type: String,
default: ''
}
},
emits: ['navigate-to-warning'],
setup(props, { emit }) {
// 处理省份点击事件
const handleProvinceClick = (provinceName) => {
emit('navigate-to-warning', provinceName)
}
return {
handleProvinceClick
}
},
data() {
return {
// 存栏率视图模式percent 或 count
livestockViewMode: 'percent',
// 全国牛总存栏量基准值
livestockBaseline: 100000000,
// 不同品种默认数据(单位:头)
livestockSpeciesData: [
{ name: '黄牛及改良牛', key: 'yellow', count: 87500000, color: '#00CDCD' },
{ name: '奶牛', key: 'dairy', count: 11000000, color: '#869DB0' },
{ name: '水牛', key: 'buffalo', count: 4750000, color: '#267EEF' },
{ name: '牦牛', key: 'yak', count: 16000000, color: '#42A2E1', globalShare: '>90%', domesticShare: '16%' }
],
// 出栏率统计校验信息
validationMessageSlaughter: '',
// 存栏率选中的地区
selectedRegionStock: '全国',
// 选中的品种
selectedBreed: '',
// 防疫统计柱状图配置
epidemicChartOption: {
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(0, 0, 0, 0.8)',
borderColor: '#00ffff',
borderWidth: 1,
textStyle: {
color: '#ffffff'
},
formatter: '{b}: {c}万头'
},
legend: {
data: ['口蹄疫防疫', '猪瘟防疫', '其他类型防疫'],
right: '10%',
top: '10%',
orient: 'vertical',
textStyle: {
color: '#ffffff',
fontSize: 12
},
itemWidth: 12,
itemHeight: 8
},
grid: {
left: '15%',
right: '35%',
bottom: '15%',
top: '15%'
},
xAxis: {
type: 'category',
data: ['口蹄疫防疫', '猪瘟防疫', '其他类型防疫'],
axisLine: {
lineStyle: {
color: '#00ffff'
}
},
axisLabel: {
color: '#ffffff',
fontSize: 10,
rotate: 0,
margin: 10
},
axisTick: {
alignWithLabel: true,
show: true
},
boundaryGap: true
},
yAxis: {
type: 'value',
name: '头数(万)',
nameTextStyle: {
color: '#00ffff',
fontSize: 12
},
axisLine: {
lineStyle: {
color: '#00ffff'
}
},
axisLabel: {
color: '#ffffff',
fontSize: 10
},
splitLine: {
lineStyle: {
color: 'rgba(0, 255, 255, 0.2)'
}
}
},
series: [{
name: '防疫统计',
type: 'bar',
data: [
{ value: 32.67, itemStyle: { color: '#00ffff' } },
{ value: 20.3, itemStyle: { color: '#84acf0' } },
{ value: 1.91, itemStyle: { color: '#96ceb4' } }
],
barWidth: '30%',
itemStyle: {
color: function(params) {
const colors = ['#00ffff', '#84acf0', '#96ceb4'];
return colors[params.dataIndex];
}
}
}]
},
// 出栏率分类数据(单位:头)与颜色(统一主题)
slaughterSpeciesData: [
{ name: '杂交改良牛', key: 'hybrid', count: 43000000, color: '#00CDCD', percent: 85 },
{ name: '奶公犊', key: 'dairy_bull_calf', count: 5000000, color: '#869DB0', percent: 10 },
{ name: '地方品种黄牛', key: 'local_yellow', minCount: 1500000, maxCount: 2000000, color: '#267EEF', minPercent: 3, maxPercent: 4 },
{ name: '其他品种', key: 'others', minCount: 500000, maxCount: 800000, color: '#42A2E1', minPercent: 1, maxPercent: 1.5 }
],
slaughterPieOption: {},
slaughterBarOption: {},
// 不同品种牛存栏率两个独立图表配置初始化为空mounted时构建
livestockPieOption: {},
livestockBarOption: {},
// 品种单价趋势图配置2025年主要品种牛单位元/公斤,活重)
priceRankingOption: {
backgroundColor: 'transparent',
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(0, 0, 0, 0.8)',
borderColor: '#00ffff',
borderWidth: 1,
textStyle: { color: '#ffffff' },
formatter: function(params) {
let result = params[0].axisValue + '<br/>';
params.forEach(param => {
result += param.marker + param.seriesName + ': ' + param.value + ' 元/公斤<br/>';
});
return result;
}
},
legend: {
data: ['杂交改良牛(西门塔尔)', '地方黄牛(秦川牛等)', '高端肉牛(安格斯/和牛)'],
textStyle: { color: '#fff', fontSize: 10 },
top: '5%',
itemWidth: 12,
itemHeight: 8
},
grid: { top: '25%', right: '8%', bottom: '15%', left: '15%' },
xAxis: {
type: 'category',
data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
name: '月份',
nameTextStyle: { color: '#00ffff', fontSize: 11 },
axisLabel: { color: '#fff', fontSize: 10, rotate: 0 },
axisLine: { lineStyle: { color: '#00ffff' } },
splitLine: { show: false }
},
yAxis: {
type: 'value',
name: '单价(元/公斤)',
nameTextStyle: { color: '#00ffff', fontSize: 11 },
axisLabel: { color: '#fff', fontSize: 10 },
axisLine: { lineStyle: { color: '#00ffff' } },
splitLine: { lineStyle: { color: 'rgba(0,255,255,0.2)' } },
min: 25,
max: 90
},
series: [
{
name: '杂交改良牛(西门塔尔)',
type: 'line',
data: [35, 36, 36, 35, 34, 33, 34, 36, 38, 40, 42, 44],
lineStyle: { color: '#267EEF', width: 2 },
itemStyle: { color: '#267EEF' },
symbol: 'circle',
symbolSize: 4,
smooth: true
},
{
name: '地方黄牛(秦川牛等)',
type: 'line',
data: [70, 71, 70, 68, 66, 65, 66, 68, 72, 75, 78, 80],
lineStyle: { color: '#00CDCD', width: 2 },
itemStyle: { color: '#00CDCD' },
symbol: 'circle',
symbolSize: 4,
smooth: true
},
{
name: '高端肉牛(安格斯/和牛)',
type: 'line',
data: [80, 81, 80, 78, 76, 75, 76, 78, 82, 85, 86, 85],
lineStyle: { color: '#42A2E1', width: 2 },
itemStyle: { color: '#42A2E1' },
symbol: 'circle',
symbolSize: 4,
smooth: true
}
]
},
// 地区-品种明细数据
detailRows: [
{ id: 1, region: '河北省', breed: '安格斯牛', stock: 68000, sold: 12000, price: 14200 },
{ id: 2, region: '山东省', breed: '荷斯坦牛', stock: 52000, sold: 9000, price: 17000 },
{ id: 3, region: '江苏省', breed: '复洲黄牛', stock: 72000, sold: 15000, price: 14500 },
{ id: 4, region: '浙江省', breed: '西门塔尔牛', stock: 61000, sold: 11000, price: 16800 },
{ id: 5, region: '新疆维吾尔自治区', breed: '夏洛莱牛', stock: 83000, sold: 13000, price: 16500 },
{ id: 6, region: '甘肃省', breed: '水牛', stock: 47000, sold: 8000, price: 17387 },
{ id: 7, region: '广东省', breed: '安格斯牛', stock: 55000, sold: 10000, price: 14200 },
{ id: 8, region: '广西壮族自治区', breed: '荷斯坦牛', stock: 43000, sold: 7500, price: 17000 },
{ id: 9, region: '湖南省', breed: '复洲黄牛', stock: 65000, sold: 12500, price: 14500 },
{ id: 10, region: '河南省', breed: '西门塔尔牛', stock: 58000, sold: 9800, price: 16800 }
],
// 耳标统计数据
earTagStats: {
completed: 45678,
planned: 52000
},
// 耳标佩戴统计堆叠柱状图配置
earTagChartOption: {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
backgroundColor: 'rgba(0, 0, 0, 0.8)',
borderColor: '#00ffff',
borderWidth: 1,
textStyle: {
color: '#ffffff'
},
formatter: function(params) {
let result = params[0].name + '<br/>';
params.forEach(function(item) {
result += item.marker + item.seriesName + ': ' + item.value + '头<br/>';
});
return result;
}
},
legend: {
data: ['已佩戴', '未佩戴'],
top: '5%',
textStyle: {
color: '#ffffff',
fontSize: 12
},
itemWidth: 12,
itemHeight: 8
},
grid: {
left: '15%',
right: '10%',
bottom: '15%',
top: '25%'
},
xAxis: {
type: 'value',
name: '数量(头)',
nameTextStyle: {
color: '#00ffff',
fontSize: 12
},
axisLine: {
lineStyle: {
color: '#00ffff'
}
},
axisLabel: {
color: '#ffffff',
fontSize: 10
},
splitLine: {
lineStyle: {
color: 'rgba(0, 255, 255, 0.2)'
}
}
},
yAxis: {
type: 'category',
data: ['东方养殖场', '西部牧场', '南山农场', '北岭牧业', '中心养殖基地'],
axisLine: {
lineStyle: {
color: '#00ffff'
}
},
axisLabel: {
color: '#ffffff',
fontSize: 10
}
},
series: [
{
name: '已佩戴',
type: 'bar',
stack: '总量',
data: [8500, 12000, 9800, 7200, 8178],
itemStyle: {
color: '#00ffff'
}
},
{
name: '未佩戴',
type: 'bar',
stack: '总量',
data: [1500, 2200, 1800, 1300, 1322],
itemStyle: {
color: '#84acf0'
}
}
]
},
// 牛只参保统计圆形进度图配置
insuranceChartOption: {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c}头 ({d}%)',
backgroundColor: 'rgba(0, 0, 0, 0.8)',
borderColor: '#00ffff',
borderWidth: 1,
textStyle: {
color: '#ffffff'
}
},
series: [
{
name: '参保统计',
type: 'pie',
radius: ['70%', '90%'],
center: ['50%', '50%'],
startAngle: 90,
avoidLabelOverlap: false,
label: {
show: false
},
labelLine: {
show: false
},
data: [
{
value: 160000,
name: '已参保',
itemStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 1,
y2: 1,
colorStops: [{
offset: 0, color: '#00ffff'
}, {
offset: 1, color: '#0080ff'
}]
}
}
},
{
value: 40000,
name: '未参保',
itemStyle: {
color: 'rgba(255, 255, 255, 0.1)'
}
}
]
}
]
},
// 不同品种年度总销售额柱状图配置
salesRevenueChartOption: {
backgroundColor: 'transparent',
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(0, 0, 0, 0.8)',
borderColor: '#00ffff',
borderWidth: 1,
textStyle: {
color: '#ffffff'
},
formatter: '{a} <br/>{b}: {c}万元'
},
grid: {
top: '15%',
right: '8%',
bottom: '15%',
left: '15%',
containLabel: true
},
xAxis: {
type: 'category',
data: ['安格斯牛', '荷斯坦牛', '夏洛莱牛', '西门塔尔牛', '利木赞牛', '其他品种'],
axisLabel: {
color: '#ffffff',
fontSize: 12,
rotate: 45
},
axisLine: {
lineStyle: {
color: '#00ffff'
}
},
axisTick: {
lineStyle: {
color: '#00ffff'
}
}
},
yAxis: {
type: 'value',
name: '销售额(万元)',
nameTextStyle: {
color: '#00ffff',
fontSize: 12
},
axisLabel: {
color: '#ffffff',
fontSize: 11
},
axisLine: {
lineStyle: {
color: '#00ffff'
}
},
axisTick: {
lineStyle: {
color: '#00ffff'
}
},
splitLine: {
lineStyle: {
color: 'rgba(0, 255, 255, 0.2)'
}
}
},
series: [
{
name: '年度总销售额',
type: 'bar',
data: [
{ value: 28500, itemStyle: { color: '#00CDCD' } },
{ value: 22800, itemStyle: { color: '#869DB0' } },
{ value: 18600, itemStyle: { color: '#267EEF' } },
{ value: 15200, itemStyle: { color: '#42A2E1' } },
{ value: 12800, itemStyle: { color: '#3BF3D3' } },
{ value: 9600, itemStyle: { color: '#00CDCD' } }
],
barWidth: '60%',
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowColor: '#00ffff'
}
}
}
]
},
}
},
computed: {
totalLivestock() {
// 以数据源计算总存栏量(万头)
return this.livestockSpeciesData.reduce((sum, s) => sum + Math.round(s.count / 10000), 0)
},
filteredDetailRows() {
if (!this.selectedBreed) {
return this.detailRows
}
return this.detailRows.filter(row => row.breed === this.selectedBreed)
}
},
methods: {
// 构建环形图(占比)
buildLivestockPieOption() {
const baseline = this.livestockBaseline
const species = this.livestockSpeciesData
const names = species.map(s => s.name)
const percents = species.map(s => Math.round(s.count / baseline * 100))
const pieData = percents.map((v, i) => ({
value: v,
name: names[i],
itemStyle: { color: species[i].color }
}))
return {
backgroundColor: 'transparent',
tooltip: {
trigger: 'item',
backgroundColor: 'rgba(0, 0, 0, 0.8)',
borderColor: '#00ffff',
borderWidth: 1,
textStyle: { color: '#ffffff' },
formatter: (params) => `${params.name}<br/>占比:${params.value}%`
},
series: [{
name: '占比(%)',
type: 'pie',
radius: ['35%', '49%'],
center: ['50%', '50%'],
avoidLabelOverlap: false,
label: {
show: true,
position: 'outside',
fontSize: 11,
color: '#ffffff',
formatter: (p) => `${p.name}\n${p.value}%`
},
labelLine: { show: true, length: 12, length2: 8, lineStyle: { color: '#ffffff', width: 1 } },
emphasis: {
label: { show: true, fontSize: 12, fontWeight: 'bold', color: '#00ffff' }
},
data: pieData
}]
}
},
// 构建柱状图(数量)
buildLivestockBarOption() {
const species = this.livestockSpeciesData
const names = species.map(s => s.name)
const countsWan = species.map(s => Math.round(s.count / 10000))
return {
backgroundColor: 'transparent',
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
backgroundColor: 'rgba(0, 0, 0, 0.8)',
borderColor: '#00ffff',
borderWidth: 1,
textStyle: { color: '#ffffff' },
formatter: (params) => {
const p = params[0]
return `${p.name}<br/>存栏量:${p.value} 万头`
}
},
grid: { left: '12%', right: '6%', top: '15%', bottom: '12%', containLabel: true },
xAxis: {
type: 'category',
data: names,
axisLine: { lineStyle: { color: '#00ffff' } },
axisLabel: { color: '#ffffff', fontSize: 11, rotate: 0 }
},
yAxis: {
type: 'value',
name: '数量(万头)',
nameTextStyle: { color: '#00ffff', fontSize: 12 },
axisLine: { lineStyle: { color: '#00ffff' } },
axisLabel: { color: '#ffffff', fontSize: 11 },
splitLine: { lineStyle: { color: 'rgba(0, 255, 255, 0.2)' } }
},
series: [{
name: '数量(万头)',
type: 'bar',
barWidth: '50%',
label: { show: true, position: 'top', color: '#eaffff', fontSize: 11, formatter: '{c} 万头' },
data: countsWan.map((v, i) => ({ value: v, itemStyle: { color: species[i].color } }))
}]
}
},
// 刷新两个图表
refreshLivestockOption() {
this.livestockPieOption = this.buildLivestockPieOption()
this.livestockBarOption = this.buildLivestockBarOption()
},
filterByBreed() {
// 品种选择变化时的处理逻辑
// filteredDetailRows计算属性会自动更新
},
updateLivestockData() {
// 当前未提供地区维度的权威数据;保持全国基准展示
this.refreshLivestockOption()
},
// 出栏率——环形图配置
buildSlaughterPieOption() {
const seriesData = this.slaughterSpeciesData.map(s => {
if (s.percent != null) {
return { value: s.percent, name: s.name, itemStyle: { color: s.color }, labelRange: `${s.percent}%` }
}
const mid = ((s.minPercent + s.maxPercent) / 2).toFixed(2)
return { value: +mid, name: s.name, itemStyle: { color: s.color }, labelRange: `${s.minPercent}-${s.maxPercent}%` }
})
return {
tooltip: {
trigger: 'item',
backgroundColor: 'rgba(0, 0, 0, 0.8)',
borderColor: '#00ffff',
borderWidth: 1,
textStyle: { color: '#ffffff' },
formatter: params => `${params.marker}${params.name}<br/>占比:${seriesData[params.dataIndex].labelRange}`
},
legend: { show: false },
series: [{
name: '出栏占比',
type: 'pie',
radius: ['35%', '49%'],
center: ['44%', '50%'],
avoidLabelOverlap: true,
label: {
show: true,
color: '#eaf7ff',
formatter: ({ dataIndex, name }) => `${name}\n${seriesData[dataIndex].labelRange}`,
fontSize: 12
},
labelLine: { show: true, length: 8, length2: 6 },
data: seriesData
}]
}
},
// 出栏率——柱状图配置(区间值展示为中位数数值,标签文本展示区间)
buildSlaughterBarOption() {
const data = this.slaughterSpeciesData.map(s => {
if (s.count != null) {
return { value: Math.round(s.count / 10000), labelText: `${Math.round(s.count / 10000)}万头`, color: s.color, name: s.name }
}
const midCount = Math.round(((s.minCount + s.maxCount) / 2) / 10000)
const labelText = `${Math.round(s.minCount / 10000)}-${Math.round(s.maxCount / 10000)}万头`
return { value: midCount, labelText, color: s.color, name: s.name }
})
return {
backgroundColor: 'transparent',
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
backgroundColor: 'rgba(0, 0, 0, 0.8)',
borderColor: '#00ffff',
borderWidth: 1,
textStyle: { color: '#ffffff' },
formatter: params => {
const p = params[0]
const d = data[p.dataIndex]
return `${p.marker}${d.name}<br/>出栏量:${d.labelText}`
}
},
grid: { top: '20%', right: '6%', bottom: '15%', left: '12%', containLabel: true },
xAxis: {
type: 'category',
data: data.map(d => d.name),
axisLabel: { color: '#ffffff', fontSize: 12 },
axisLine: { lineStyle: { color: '#00ffff' } },
axisTick: { lineStyle: { color: '#00ffff' } }
},
yAxis: {
type: 'value',
name: '出栏量(万头)',
nameTextStyle: { color: '#00ffff', fontSize: 12 },
axisLabel: { color: '#ffffff', fontSize: 11 },
axisLine: { lineStyle: { color: '#00ffff' } },
axisTick: { lineStyle: { color: '#00ffff' } },
splitLine: { lineStyle: { color: 'rgba(0, 255, 255, 0.2)' } }
},
series: [{
name: '出栏量',
type: 'bar',
data: data.map(d => ({ value: d.value, itemStyle: { color: d.color } })),
barWidth: '60%',
label: {
show: true,
position: 'top',
color: '#eaf7ff',
formatter: ({ dataIndex }) => data[dataIndex].labelText,
fontSize: 11
}
}]
}
},
refreshSlaughterOption() {
this.slaughterPieOption = this.buildSlaughterPieOption()
this.slaughterBarOption = this.buildSlaughterBarOption()
},
validateSlaughterData() {
const categories = ['hybrid', 'dairy_bull_calf', 'local_yellow', 'others']
const keysPresent = new Set(this.slaughterSpeciesData.map(s => s.key))
if (!categories.every(k => keysPresent.has(k))) {
this.validationMessageSlaughter = '数据完整性异常:缺少分类项,请检查数据源。'
return false
}
let sum = 0
this.slaughterSpeciesData.forEach(s => {
if (s.percent != null) sum += s.percent
else sum += (s.minPercent + s.maxPercent) / 2
})
const ok = Math.abs(sum - 100) <= 0.5
this.validationMessageSlaughter = ok ? '' : `占比合计为${sum.toFixed(2)}%未满足100%±0.5%校验。`
return ok
},
ingestSlaughterRecords(records) {
if (!Array.isArray(records)) return
const map = { hybrid: 0, dairy_bull_calf: 0, local_yellow: 0, others: 0 }
for (let i = 0; i < records.length; i++) {
const r = records[i]
if (map[r.categoryKey] != null) map[r.categoryKey] += r.count || 0
}
this.slaughterSpeciesData = this.slaughterSpeciesData.map(s => {
if (s.count != null) return { ...s, count: map[s.key] || s.count }
if (map[s.key] && map[s.key] > 0) {
const val = map[s.key]
return { ...s, minCount: Math.min(val, s.minCount ?? val), maxCount: Math.max(val, s.maxCount ?? val) }
}
return s
})
this.refreshSlaughterOption()
this.validateSlaughterData()
}
},
mounted() {
// 页面加载时分别初始化数据
this.refreshLivestockOption()
this.refreshSlaughterOption()
this.validateSlaughterData()
}
}
</script>
<style scoped>
.dashboard-container {
/* 移除flex布局使用App.vue中的fixed布局 */
background: transparent;
color: #ffffff;
font-family: 'Microsoft YaHei', sans-serif;
}
/* 地图容器样式 */
.map-container {
position: relative;
width: 100%;
height: 100%; /* 从68%改为100%,让地图占满整个中间区域 */
margin-top: 0; /* 移除负边距 */
}
.lowest-price-panel h3 {
color: #00d4ff;
font-size: 16px;
margin: 0 0 15px 0;
text-align: left;
font-weight: bold;
}
.price-item {
background: rgba(0, 255, 255, 0.1);
border: 1px solid rgba(0, 255, 255, 0.3);
border-radius: 6px;
padding: 10px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
transition: all 0.3s ease;
min-height: 80px; /* 设置最小高度确保内容显示 */
}
.price-item:hover {
background: rgba(0, 255, 255, 0.2);
border-color: #00d4ff;
transform: translateY(-2px);
}
.breed-name {
color: #00d4ff;
font-size: 13px; /* 增加字体大小 */
font-weight: bold;
margin-bottom: 6px; /* 增加底部间距 */
text-align: center;
}
.province-info {
display: flex;
flex-direction: column;
align-items: center;
gap: 3px; /* 增加间距 */
}
.province {
color: #ffffff;
font-size: 12px; /* 增加字体大小 */
}
.price {
color: #00ff88;
font-size: 11px; /* 增加字体大小 */
font-weight: bold;
}
/* 左右侧面板容器边框 */
.dashboard-left::before,
.dashboard-right::before {
content: '';
position: absolute;
top: 0px;
left: 10px;
right: 10px;
border: 2px solid #00d4ff;
border-radius: 8px;
pointer-events: none;
box-shadow:
0 0 10px rgba(0, 212, 255, 0.3),
inset 0 0 10px rgba(0, 212, 255, 0.1);
}
.dashboard-left::after,
.dashboard-right::after {
content: '';
position: absolute;
top: 10px;
left: 10px;
right: 10px;
/* bottom: 10px; */
border: 1px solid rgba(0, 212, 255, 0.2);
border-radius: 6px;
pointer-events: none;
}
/* 合并模块样式 */
.combined-panel {
height: 70vh; /* 进一步增加到70%视口高度 */
}
.combined-content {
display: flex;
gap: 15px;
height: calc(100% - 60px); /* 减去标题高度 */
}
/* 上下布局样式 */
.combined-content.vertical-layout {
flex-direction: column;
}
.chart-section {
flex: 1;
display: flex;
flex-direction: column;
background: rgba(0, 20, 40, 0.3);
border-radius: 4px;
}
/* 上下布局时的图表区域样式 */
.vertical-layout .chart-section {
min-height: 220px; /* 增加最小高度,提供更多空间 */
}
.chart-section h4 {
color: #00d4ff;
font-size: 14px;
margin: 0 0 10px 0;
text-align: left;
border-bottom: 1px solid rgba(0, 212, 255, 0.2);
}
.sales-section .chart,
.livestock-section .livestock-chart {
flex: 1;
min-height: 200px; /* 减小最小高度 */
}
.echarts-container {
flex: 1;
display: flex;
flex-direction: column;
}
/* 面板样式 */
.panel {
background: rgba(7, 59, 68, 0.15);
border: 1px solid rgba(0, 212, 255, 0.3);
border-radius: 6px;
margin-bottom: 5px;
backdrop-filter: blur(5px);
position: relative;
overflow: hidden;
box-shadow:
0 0 8px rgba(0, 212, 255, 0.2),
inset 0 0 8px rgba(0, 212, 255, 0.05);
}
.panel-header {
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid rgba(0, 212, 255, 0.2);
margin-bottom: 10px;
}
.panel-header::before {
content: '';
position: absolute;
bottom: 0;
left: 15px;
width: 30px;
height: 2px;
background: #00d4ff;
box-shadow: 0 0 4px rgba(0, 212, 255, 0.6);
}
.panel-header h3 {
color: #ffffff;
font-size: 16px;
padding-bottom: 5px;
font-weight: bold;
position: relative;
z-index: 1;
}
.panel-header h3::before {
content: '';
position: absolute;
left: -10px;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 4px;
background: #00d4ff;
border-radius: 50%;
box-shadow: 0 0 6px rgba(0, 212, 255, 0.8);
}
.region-select:hover,
.breed-selector:hover {
border-color: #00ffff;
box-shadow: 0 0 8px rgba(0, 255, 255, 0.3);
}
.region-select option,
.breed-selector option {
background: rgba(0, 20, 40, 0.95);
color: #ffffff;
padding: 4px;
}
/* 存栏总数统计样式 */
.livestock-panel .echarts-container {
display: flex;
flex-direction: row;
gap: 10px; /* 两图间距约16px */
height:230px;
padding: 0 5px 5px 5px;
}
.total-display {
text-align: center;
/* margin-bottom: 1px; */
}
.total-number {
font-size: 24px;
font-weight: bold;
color: #00ffff;
}
.total-label {
font-size: 12px;
color: #cccccc;
margin-top: 5px;
}
.livestock-chart {
flex: 1;
width: 100%;
height: 350px;
}
/* 新增:存栏率的两个图表容器 */
.livestock-pie {
flex: 0 0 40%;
height: 100%;
}
.livestock-bar {
flex: 1;
height: 100%;
}
/* 统一图例样式,置于两图下方 */
.livestock-legend {
display: flex;
flex-wrap: wrap;
gap: 10px 10px;
align-items: center;
}
.livestock-legend .legend-item {
display: flex;
align-items: center;
gap: 6px;
}
.livestock-legend .legend-color {
width: 12px;
height: 12px;
border-radius: 2px;
box-shadow: 0 0 6px rgba(0, 212, 255, 0.5);
}
.livestock-legend .legend-label {
color: #eaf7ff;
font-size: 12px;
}
/* 小屏幕下纵向排列 */
@media (max-width: 768px) {
.livestock-panel .echarts-container {
flex-direction: column;
height: auto;
}
.livestock-pie,
.livestock-bar {
width: 100%;
height: 280px;
}
}
/* 出栏率统计样式 */
.slaughter-panel {
height: 330px;
}
.slaughter-panel .echarts-container {
display: flex;
flex-direction: row;
align-items: stretch;
gap: 16px;
height: 200px;
}
.slaughter-pie {
flex: 0 0 42%;
height: 100%;
}
.slaughter-bar {
flex: 1;
height: 100%;
}
.slaughter-legend {
display: flex;
flex-wrap: wrap;
gap: 10px 10px;
align-items: center;
margin-top: 8px;
}
.slaughter-legend .legend-item { display: flex; align-items: center; gap: 6px; }
.slaughter-legend .legend-color { width: 12px; height: 12px; border-radius: 2px; box-shadow: 0 0 6px rgba(0, 212, 255, 0.5); }
.slaughter-legend .legend-label { color: #eaf7ff; font-size: 12px; }
.validation-message { margin-top: 6px; color: #ffcf6e; font-size: 12px; }
@media (max-width: 768px) {
.slaughter-panel { height: auto; }
.slaughter-panel .echarts-container { flex-direction: column; height: auto; }
.slaughter-pie, .slaughter-bar { width: 100%; height: 280px; }
}
.flip-card:hover .flip-card-inner {
transform: rotateY(180deg);
}
.flip-card-front {
position: absolute;
width: 100%;
height: 100%;
backface-visibility: hidden;
background: rgba(0, 255, 255, 0.15);
border: 1px solid rgba(0, 255, 255, 0.3);
border-radius: 8px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
backdrop-filter: blur(10px);
}
.card-number {
font-size: 24px;
font-weight: bold;
color: #00ffff;
margin-bottom: 8px;
}
.card-label {
font-size: 12px;
color: #cccccc;
text-align: center;
line-height: 1.2;
}
.chart-container {
flex: 1;
min-height: 200px;
}
.ear-tag-chart {
width: 100%;
height:93%;
}
/* 品种单价排行榜样式 */
.price-panel .price-content {
height: 280px;
padding: 0 15px 10px 10px;
}
.price-chart {
width: 100%;
height: 100%;
}
/* 明细表样式 */
.detail-table {
width: 100%;
display: flex;
flex-direction: column;
gap: 12px;
padding: 15px;
height: 520px;
overflow-y: auto;
}
.detail-header, .detail-row {
display: grid;
grid-template-columns: 1.2fr 1fr 1fr 1fr 1.2fr;
gap: 15px;
align-items: center;
font-size: 14px;
}
.detail-header {
color: #84acf0;
border-bottom: 1px solid rgba(132, 172, 240, 0.2);
padding-bottom: 8px;
font-weight: bold;
}
.detail-row {
color: #fff;
/* padding: 6px 0; */
border-bottom: 1px dashed rgba(132, 172, 240, 0.15);
}
.detail-row:hover {
background: rgba(132, 172, 240, 0.1);
border-radius: 4px;
}
/* 销售额柱状图模块样式 */
.sales-revenue-panel {
margin-top: 5px;
height: 280px; /* 缩小高度 */
background: rgba(0, 20, 40, 0.3);
border: 1px solid rgba(0, 212, 255, 0.2);
border-radius: 4px;
padding: 15px;
}
.sales-revenue-chart {
width: 100%;
height: calc(100% - 40px); /* 调整高度计算 */
margin-top: 15px; /* 添加上边距,将图表往下移 */
}
.company-item {
display: flex;
align-items: center;
margin-bottom: 8px;
}
.company-bar {
width: 80px;
height: 12px;
background: rgba(255, 255, 255, 0.1);
border-radius: 6px;
margin-right: 10px;
overflow: hidden;
}
.company-fill {
height: 100%;
border-radius: 6px;
transition: width 0.3s ease;
}
.company-name {
font-size: 10px;
color: #ffffff;
flex: 1;
line-height: 1.2;
}
/* 存栏率模块工具栏与数据说明 */
.panel-tools {
display: flex;
gap: 8px;
margin-left: 10px;
}
.tool-btn {
background: rgba(0, 212, 255, 0.15);
border: 1px solid rgba(0, 212, 255, 0.4);
color: #ffffff;
border-radius: 6px;
padding: 4px 8px;
font-size: 12px;
cursor: pointer;
}
.tool-btn:hover {
background: rgba(0, 212, 255, 0.25);
}
.data-notes {
margin-top: 10px;
min-height: 10%;
display: flex;
flex-direction: column;
gap: 4px;
color: #00d4ff;
font-size: 12px;
}
.data-source {
color: rgba(255, 255, 255, 0.7);
}
</style>