Files
datav---Cattle-Industry/src/components/Price.vue
2025-12-02 17:27:45 +08:00

850 lines
28 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script>
import { ref, computed, onMounted, watch } from 'vue'
import { use } from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
import { LineChart, PieChart, BarChart } from 'echarts/charts'
import { TitleComponent, TooltipComponent, GridComponent, LegendComponent } from 'echarts/components'
import VChart from 'vue-echarts'
use([CanvasRenderer, LineChart, PieChart, BarChart, TitleComponent, TooltipComponent, GridComponent, LegendComponent])
export default {
name: 'Price',
components: { VChart },
props: {
selectedProvince: {
type: String,
default: ''
}
},
setup(props) {
const provinceNameMap = {
BJ: '北京市', TJ: '天津市', HE: '河北省', SX: '山西省', NM: '内蒙古自治区',
LN: '辽宁省', JL: '吉林省', HL: '黑龙江省', SH: '上海市', JS: '江苏省',
ZJ: '浙江省', AH: '安徽省', FJ: '福建省', JX: '江西省', SD: '山东省',
HA: '河南省', HB: '湖北省', HN: '湖南省', GD: '广东省', GX: '广西壮族自治区',
HI: '海南省', CQ: '重庆市', SC: '四川省', GZ: '贵州省', YN: '云南省',
XZ: '西藏自治区', SN: '陕西省', GS: '甘肃省', QH: '青海省', NX: '宁夏回族自治区', XJ: '新疆维吾尔自治区'
}
const provinceName = computed(() => provinceNameMap[props.selectedProvince] || props.selectedProvince || '未知地区')
// 省份基础价(元/斤),用于生成示例数据
const basePriceMap = {
NX: 6.8, BJ: 7.2, TJ: 6.9, HI: 7.0, CQ: 6.7, HE: 6.6, SD: 6.9, HB: 6.8
}
// 牛品种列表(示例)
const breedListMap = {
default: ['西门塔尔牛', '利木赞牛', '夏洛来牛', '本地黄牛'],
NX: ['西门塔尔牛', '本地黄牛', '海福特牛'],
BJ: ['利木赞牛', '安格斯牛', '夏洛来牛'],
HI: ['本地黄牛', '安格斯牛'],
CQ: ['本地黄牛', '西门塔尔牛']
}
const unit = ref('元/斤')
const days = ref([])
const priceSeries = ref([])
// 省内各地区牛数据列表
const regionRows = ref([])
const selectedRow = ref(null)
const todayPrice = computed(() => (priceSeries.value[priceSeries.value.length - 1] ?? 0))
const max7d = computed(() => (priceSeries.value.length ? Math.max(...priceSeries.value) : 0))
const min7d = computed(() => (priceSeries.value.length ? Math.min(...priceSeries.value) : 0))
const avg7d = computed(() => {
if (!priceSeries.value.length) return 0
const sum = priceSeries.value.reduce((acc, v) => acc + v, 0)
return +(sum / priceSeries.value.length).toFixed(2)
})
// 当日均价(以省内各地区列表的当日价格求平均;无列表则回退为趋势当日值)
const avgToday = computed(() => {
if (selectedRow.value && selectedRow.value.price) {
return +(selectedRow.value.price).toFixed(2)
}
if (regionRows.value.length) {
const sum = regionRows.value.reduce((acc, r) => acc + (r.price || 0), 0)
return +(sum / regionRows.value.length).toFixed(2)
}
return todayPrice.value
})
const breeds = computed(() => breedListMap[props.selectedProvince] || breedListMap.default)
// 日期范围与重量类别筛选
const range = ref(7) // 7/30/60
const weight = ref('normal') // normal/400/500
const setRange = (v) => { range.value = v; genData(selectedRow.value?.id) }
const setWeight = (w) => { weight.value = w; genData(selectedRow.value?.id) }
const isUnknownRegion = (name) => {
const n = (name ?? '').toString()
return n === '未知地区' || n.startsWith('未知地区 ')
}
const chartOption = computed(() => ({
title: { text: `${provinceName.value} 牛价趋势`, left: 'center', textStyle: { color: '#00d4ff' } },
tooltip: { trigger: 'axis' },
grid: { left: 40, right: 20, top: 60, bottom: 40 },
xAxis: { type: 'category', data: days.value, axisLabel: { color: '#cfefff' }, axisLine: { lineStyle: { color: '#2e6ba8' } } },
yAxis: { type: 'value', axisLabel: { color: '#cfefff' }, splitLine: { lineStyle: { color: 'rgba(0,212,255,0.2)' } } },
series: [{
name: '牛价', type: 'line', smooth: true, data: priceSeries.value,
lineStyle: { color: '#00d4ff', width: 2 },
itemStyle: { color: '#00ffcc' },
areaStyle: { color: 'rgba(0,212,255,0.15)' }
}]
}))
// 品种占比统计(环形图)
const breedPieOption = computed(() => {
// 按左侧列表统计各品种出现次数作为占比;无数据时按照品种列表均匀分配
const counts = new Map()
if (regionRows.value.length) {
regionRows.value.forEach(r => {
const k = r.breed || '未知品种'
counts.set(k, (counts.get(k) || 0) + 1)
})
} else {
// 示例分布:根据索引给出不同权重
breeds.value.forEach((b, i) => {
counts.set(b, 10 + (i * 5))
})
}
const data = Array.from(counts.entries()).map(([name, value]) => ({ name, value }))
// 指定配色:#FF7A28#00E9FF#4E73DF#0B1424
const brandColors = ['#FF7A28', '#00E9FF', '#4E73DF', '#0B1424','#61eaff']
return {
color: brandColors,
title: { text: '品种占比统计', left: 'center', top: 6, textStyle: { color: '#eaf7ff', fontSize: 14 } },
legend: { right: 20, top: 'middle', orient: 'vertical', textStyle: { color: '#cfefff' }, itemWidth: 12, itemHeight: 12 },
tooltip: { trigger: 'item' },
series: [{
type: 'pie',
radius: ['48%', '70%'],
center: ['50%', '55%'],
avoidLabelOverlap: true,
label: { show: false },
emphasis: { label: { show: true, fontSize: 14, fontWeight: 'bold', formatter: '{b}: {d}%' } },
itemStyle: {
borderColor: '#011819',
borderWidth: 6,
borderRadius: 8
},
data
}]
}
})
// 优先通过接口生成省内地区牛数据列表,失败则回退示例数据
const fetchProvinceRegionRows = async () => {
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 today = new Date()
const fmt = (d) => `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`
// 仅保留当前省份的数据
const filtered = list.filter(item => {
const p = item.province ?? item.provinceName ?? ''
return p === provinceName.value
})
const rows = filtered.map((item, idx) => ({
id: item.id ?? `${props.selectedProvince}-${idx}`,
date: fmt(today),
region: item.location ?? '',
breed: item.type ?? item.breed ?? '',
price: Number(item.price),
delta: 0,
up: false
})).filter(r => r.region && r.breed && Number.isFinite(r.price))
if (rows.length > 0) {
regionRows.value = rows
return true
}
return false
} catch (e) {
console.warn('[Price] 获取省内地区牛数据失败:', e)
return false
}
}
// 生成省内地区牛数据列表示例固定为10行
const genRegionRows = () => {
const today = new Date()
const fmt = (d) => `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`
const regionTypes = [
'省会城区','市辖区','回族自治县','张北县','市郊区',
'开发区','交易市场','牧区','新区','工业园区'
]
const regions = regionTypes.map(t => `${provinceName.value} ${t}`)
const base = basePriceMap[props.selectedProvince] ?? 6.8
regionRows.value = regions.slice(0, 10).map((r, idx) => {
const d = new Date(today)
const fluct = (Math.sin(idx) * 0.08) + (Math.random() * 0.12 - 0.06)
const breed = (breeds.value[idx % breeds.value.length])
const price = +((base + fluct)).toFixed(2)
const delta = +((Math.random() * 0.6 - 0.3)).toFixed(2)
return {
date: fmt(d),
breed,
region: r,
price,
delta,
up: delta > 0,
id: `${props.selectedProvince}-${idx}`
}
})
}
const genData = (regionId = null) => {
// 根据筛选生成最近 N 天日期与价格数据(可按地区与重量微调)
const baseBase = basePriceMap[props.selectedProvince] ?? 6.8
const regionOffset = regionId ? (parseInt(regionId.split('-')[1]) || 0) * 0.03 : 0
const weightOffset = weight.value === '400' ? 0.15 : (weight.value === '500' ? 0.25 : 0)
const base = baseBase + regionOffset + weightOffset
const today = new Date()
const d = []
const p = []
const count = range.value
for (let i = count - 1; i >= 0; i--) {
const dt = new Date(today)
dt.setDate(today.getDate() - i)
d.push(`${dt.getMonth() + 1}-${String(dt.getDate()).padStart(2, '0')}`)
// 价格在基础价附近小幅波动
const fluct = (Math.sin(i) * 0.08) + (Math.random() * 0.12 - 0.06)
p.push(+((base + fluct)).toFixed(2))
}
days.value = d
priceSeries.value = p
}
const handleRowClick = (row) => {
selectedRow.value = row
genData(row?.id)
}
// 年度统计数据(示例)
const statsYears = ref([ 2023, 2024,2025])
const stockYears = ref([2023, 2024, 2025])
const stockCounts = ref([]) // 存栏数量(万头)
const slaughterCounts = ref([]) // 出栏数量(万头)
let stockAbortController = null
// provinces 接口参数映射:与 Home.vue 保持一致,以避免名称差异导致无数据
const toApiProvinceParam = (name) => {
const map = {
'内蒙古自治区': '内蒙古',
'四川省': '四川',
'新疆维吾尔自治区': '新疆',
'西藏自治区': '西藏',
'宁夏回族自治区': '宁夏',
'广西壮族自治区': '广西',
'河北省': '河北',
'山东省': '山东',
'黑龙江省': '黑龙江',
'吉林省': '吉林',
'云南省': '云南',
'甘肃省': '甘肃',
'青海省': '青海',
'贵州省': '贵州',
'安徽省': '安徽',
}
return map[name] ?? name
}
const fetchStockData = async () => {
// 取消上一次未完成的请求
if (stockAbortController) {
stockAbortController.abort()
}
stockAbortController = new AbortController()
try {
if (!provinceName.value) return
const apiParam = toApiProvinceParam(provinceName.value)
const url = `/api/cattle-data/provinces?province=${encodeURIComponent(apiParam)}`
const res = await fetch(url, { signal: stockAbortController.signal })
const raw = await res.json()
let data = raw
if (raw.code === 200 && raw.data) {
data = raw.data
}
let target = data
if (Array.isArray(data)) {
target = data.find(d => d.province === provinceName.value || d.provinceName === provinceName.value)
if (!target && data.length > 0) target = data[0]
}
if (target) {
stockCounts.value = [
Number(target.inventory23th) || 0,
Number(target.inventory24th) || 0,
Number(target.inventory25th) || 0
]
// 同步设置出栏统计(与存栏相同来源)
slaughterCounts.value = [
Number(target.slaughter23th) || 0,
Number(target.slaughter24th) || 0,
Number(target.slaughter25th) || 0
]
} else {
stockCounts.value = [0, 0, 0]
slaughterCounts.value = [0, 0, 0]
}
} catch (e) {
if (e.name === 'AbortError') return
console.error('获取存栏数据失败', e)
stockCounts.value = [0, 0, 0]
slaughterCounts.value = [0, 0, 0]
} finally {
stockAbortController = null
}
}
const genStats = () => {
// 统一使用接口返回值,不再生成示例数据
fetchStockData()
}
const stockBarOption = computed(() => ({
title: { text: '存栏统计(万头)', left: 'center', textStyle: { color: '#eaf7ff' } },
tooltip: { trigger: 'axis' },
grid: { left: 40, right: 20, top: 50, bottom: 40 },
xAxis: { type: 'category', data: stockYears.value, axisLabel: { color: '#cfefff' }, axisLine: { lineStyle: { color: '#2e6ba8' } } },
yAxis: { type: 'value', axisLabel: { color: '#cfefff' }, splitLine: { lineStyle: { color: 'rgba(0,212,255,0.2)' } } },
series: [{
name: '存栏', type: 'bar', data: stockCounts.value,
itemStyle: { color: '#00d4ff' },
barWidth: '40%'
}]
}))
const slaughterLineOption = computed(() => ({
title: { text: '出栏统计(万头)', left: 'center', textStyle: { color: '#eaf7ff' } },
tooltip: { trigger: 'axis' },
grid: { left: 40, right: 20, top: 50, bottom: 40 },
xAxis: { type: 'category', data: statsYears.value, axisLabel: { color: '#cfefff' }, axisLine: { lineStyle: { color: '#2e6ba8' } } },
yAxis: { type: 'value', axisLabel: { color: '#cfefff' }, splitLine: { lineStyle: { color: 'rgba(0,212,255,0.2)' } } },
series: [{
name: '出栏', type: 'line', smooth: true, data: slaughterCounts.value,
lineStyle: { color: '#29e3ff', width: 2 },
itemStyle: { color: '#61eaff' },
areaStyle: { color: 'rgba(0,212,255,0.12)' }
}]
}))
const yesterdayDelta = computed(() => {
if (!priceSeries.value.length || priceSeries.value.length < 2) return 0
const n = priceSeries.value.length
return +(priceSeries.value[n - 1] - priceSeries.value[n - 2]).toFixed(2)
})
const trendText = computed(() => {
const d = yesterdayDelta.value
if (Math.abs(d) < 0.01) return '平稳'
return d > 0 ? '上涨' : '下跌'
})
const trendSymbol = computed(() => {
const d = yesterdayDelta.value
if (Math.abs(d) < 0.01) return '-'
return d > 0 ? '↑' : '↓'
})
// 统一在选中省份变化时加载数据,并在初始化时立即执行一次
watch(() => props.selectedProvince, async (newVal, oldVal) => {
if (newVal === oldVal) return
selectedRow.value = null
const ok = await fetchProvinceRegionRows()
if (!ok) genRegionRows()
genData()
genStats()
}, { immediate: true })
return { unit, provinceName, breeds, todayPrice, max7d, min7d, avg7d, avgToday, days, priceSeries, chartOption, breedPieOption, stockBarOption, slaughterLineOption, regionRows, selectedRow, handleRowClick, isUnknownRegion, range, weight, setRange, setWeight, yesterdayDelta, trendText, trendSymbol }
}
}
</script>
<template>
<div class="price-page">
<div class="price-header">
<h2><span :class="{ highlight: isUnknownRegion(provinceName) }">{{ provinceName }}</span> 牛价行情</h2>
<!-- <div class="unit">单位<span>{{ unit }}</span></div> -->
</div>
<div class="price-content">
<!-- 第一行左侧牛数据列表 + 右侧价格详情等高 -->
<div class="card region-card">
<div class="panel-header">
<div class="diamond-icon"></div>
<h3>省内各地区牛数据列表</h3>
</div>
<div class="region-table">
<div class="table-header">
<div class="th">序号</div>
<div class="th">时间</div>
<div class="th">地区</div>
<div class="th">品类</div>
<div class="th">价格{{ unit }}</div>
</div>
<div class="table-body">
<div class="table-row" v-for="(row, idx) in regionRows" :key="row.id" @click="handleRowClick(row)" :class="{ active: selectedRow && selectedRow.id === row.id }">
<div class="td">{{ idx + 1 }}</div>
<div class="td">{{ row.date }}</div>
<div class="td">{{ row.region }}</div>
<div class="td">{{ row.breed }}</div>
<div class="td">{{ row.price.toFixed(2) }}{{ unit }}</div>
</div>
</div>
</div>
</div>
<div class="card detail-card">
<div class="panel-header">
<div class="diamond-icon"></div>
<h3>
<span :class="{ highlight: isUnknownRegion(selectedRow ? selectedRow.region : provinceName) }">
{{ (selectedRow ? selectedRow.region : provinceName) }}
</span>
价格详情
</h3>
</div>
<div class="detail-content">
<!-- <div class="today-price-line">
<div class="labels">
<span class="label">今日批发均价</span>
<span class="compare">相比昨日 <span :class="yesterdayDelta === 0 ? 'change-zero' : (yesterdayDelta > 0 ? 'change-up' : 'change-down')">{{ trendText }} {{ yesterdayDelta.toFixed(2) }}</span> <span class="symbol">{{ trendSymbol }}</span></span>
</div>
<div class="price-display">
<span class="currency">¥</span>
<span class="value">{{ avgToday.toFixed(2) }}</span>
<span class="unit">{{ unit }}</span>
</div>
</div> -->
<!-- <div class="filters">
<div class="weight-group">
<button :class="['filter-btn', { active: weight === 'normal' }]" @click="setWeight('normal')">通货</button>
<button :class="['filter-btn', { active: weight === '400' }]" @click="setWeight('400')">400斤</button>
<button :class="['filter-btn', { active: weight === '500' }]" @click="setWeight('500')">500斤</button>
</div>
<div class="range-group">
<button :class="['filter-btn', { active: range === 7 }]" @click="setRange(7)">近7天</button>
<button :class="['filter-btn', { active: range === 30 }]" @click="setRange(30)">近30天</button>
<button :class="['filter-btn', { active: range === 60 }]" @click="setRange(60)">近60天</button>
</div>
</div> -->
<v-chart class="trend-chart" :option="chartOption" autoresize />
</div>
</div>
<!-- 第二行左侧品种占比 + 中间存栏 + 右侧出栏三列等宽 -->
<div class="card breed-card">
<div class="panel-header">
<div class="diamond-icon"></div>
<h3>品种占比统计</h3>
</div>
<v-chart class="breed-donut" :option="breedPieOption" autoresize />
</div>
<div class="card stock-card">
<div class="panel-header">
<div class="diamond-icon"></div>
<h3>存栏统计</h3>
</div>
<v-chart class="stock-chart" :option="stockBarOption" autoresize />
</div>
<div class="card slaughter-card">
<div class="panel-header">
<div class="diamond-icon"></div>
<h3>出栏统计</h3>
</div>
<v-chart class="slaughter-chart" :option="slaughterLineOption" autoresize />
</div>
</div>
</div>
</template>
<style scoped>
.price-page {
width: 100%;
height: calc(100vh - 100px);
padding: 16px 24px;
display: flex;
flex-direction: column;
gap: 16px;
background: #011819; /* 与预警监测页背景一致 */
position: relative;
overflow: hidden; /* 页面高度固定,禁用整体滚动 */
}
.price-page::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background:
rgba(0, 212, 255, 0.05),
rgba(0, 212, 255, 0.03),
rgba(0, 212, 255, 0.02);
pointer-events: none;
}
.price-page > * { position: relative; z-index: 1; }
.price-header {
display: flex;
justify-content: center;
align-items: center;
background: rgba(255,255,255,0.06);
border: 1px solid rgba(0,212,255,0.25);
padding: 10px 16px;
border-radius: 8px;
}
.price-header h2 {
font-size: 18px;
color: #eaf7ff;
}
.unit span {
color: #00ffcc;
}
.price-content {
display: grid;
grid-template-columns: repeat(6, 1fr); /* 6列方便分配 3:3 和 2:2:2 */
grid-template-rows: 1fr 1fr; /* 两行等高 */
gap: 16px;
flex: 1; /* 占满剩余高度 */
min-height: 0; /* 允许内部滚动 */
}
.card {
background: rgba(7, 59, 68, 0.15); /* 参考预警监测 .panel 背景 */
border: 1px solid rgba(0, 212, 255, 0.3);
border-radius: 12px;
padding: 16px 18px;
backdrop-filter: blur(10px);
position: relative;
overflow: hidden;
height: 100%; /* 与所在网格行高度一致 */
display: flex;
flex-direction: column; /* 让内部内容自适应高度 */
}
/* 网格跨度设置 */
.region-card { grid-column: span 3; }
.detail-card { grid-column: span 3; }
.breed-card { grid-column: span 2; }
.stock-card { grid-column: span 2; }
.slaughter-card { grid-column: span 2; }
.card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
background: #00d4ff;
opacity: 0.6;
}
.card-title {
color: #bfe9ff;
font-size: 14px;
margin-bottom: 8px;
}
/* 统一标题为预警监测页风格 */
.panel-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 8px;
border-bottom: 1px solid rgba(0, 212, 255, 0.2);
padding-bottom: 6px;
}
.panel-header h3 {
color: #ffffff;
font-size: 16px;
font-weight: 700;
margin: 0;
}
.highlight { color: #00d4ff; }
.diamond-icon {
width: 12px;
height: 12px;
background: #00d4ff;
transform: rotate(45deg);
box-shadow: 0 0 10px rgba(0, 212, 255, 0.5);
}
.breed-list {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.breed-item {
background: rgba(0,212,255,0.12);
border: 1px solid rgba(0,212,255,0.35);
color: #eaf7ff;
font-size: 12px;
padding: 6px 10px;
border-radius: 16px;
}
.price-today .value {
color: #00ffcc;
font-size: 26px;
font-weight: bold;
margin-right: 8px;
}
.price-today .unit {
color: #cfefff;
}
/* 移除旧的 .right-stats, .left-info, .stats-bottom 相关样式 */
.breed-card {
background: rgba(255,255,255,0.06);
border: 1px solid rgba(0,212,255,0.25);
border-radius: 8px;
padding: 10px 12px;
}
.breed-donut {
width: 100%;
flex: 1;
min-height: 0;
margin: 0 auto;
}
.detail-card {
background: rgba(255,255,255,0.06);
border: 1px solid rgba(0,212,255,0.25);
border-radius: 8px;
padding: 8px 10px; /* 缩小内边距,降低模块总高度 */
}
.detail-content {
display: flex;
flex-direction: column;
gap: 8px; /* 收紧纵向间距 */
flex: 1;
min-height: 0;
}
.stock-card, .slaughter-card {
background: rgba(255,255,255,0.06);
border: 1px solid rgba(0,212,255,0.25);
border-radius: 8px;
padding: 10px 12px;
}
.stock-chart, .slaughter-chart {
width: 100%;
flex: 1;
min-height: 0;
}
.today-price-line {
display: flex;
justify-content: space-between;
align-items: flex-end;
gap: 12px;
}
.today-price-line .labels { color: #cfefff; font-size: 14px; }
.today-price-line .price-display { display: flex; align-items: baseline; gap: 6px; }
.today-price-line .currency { color: #ff7a28; font-size: 22px; font-weight: 700; }
.today-price-line .value { color: #ff7a28; font-size: 32px; font-weight: 800; }
.today-price-line .unit { color: #ff7a28; font-size: 14px; font-weight: 600; }
.today-price-line .symbol { color: #cfefff; margin-left: 6px; }
.filters { display: flex; justify-content: space-between; align-items: center; }
.filter-btn {
background: rgba(255,255,255,0.06);
border: 1px solid rgba(0,212,255,0.35);
color: #eaf7ff;
font-size: 13px;
padding: 6px 12px;
border-radius: 20px;
cursor: pointer;
transition: all 0.2s ease;
margin-right: 8px;
}
.filter-btn.active { background: rgba(0,212,255,0.2); border-color: #00d4ff; color: #ffffff; }
.filter-btn:hover { background: rgba(0,212,255,0.12); }
.weight-group, .range-group { display: flex; align-items: center; }
.region-table {
display: flex;
flex-direction: column;
gap: 6px;
}
.table-header, .table-row {
display: grid;
grid-template-columns: 0.6fr 1fr 1.6fr 1.2fr 1fr; /* 新增序号列后为五列 */
gap: 10px;
align-items: center;
}
.table-header {
padding: 8px 0;
border-bottom: 1px solid rgba(0, 212, 255, 0.2);
color: #ffffff;
font-weight: 600;
}
.table-body {
display: flex;
flex-direction: column;
}
/* 缩小左侧“省内各地区牛数据列表”模块高度 - 移除固定高度 */
.region-card .region-table {
height: auto;
flex: 1; /* 占据剩余空间 */
min-height: 0; /* 允许压缩 */
display: flex;
flex-direction: column;
}
.region-card .region-table .table-body {
height: auto;
flex: 1; /* 占据剩余空间 */
overflow-y: auto; /* 允许滚动 */
min-height: 0; /* 允许压缩 */
}
/* 自定义滚动条样式 */
.region-card .region-table .table-body::-webkit-scrollbar {
width: 6px;
}
.region-card .region-table .table-body::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.05);
border-radius: 3px;
}
.region-card .region-table .table-body::-webkit-scrollbar-thumb {
background: rgba(0, 212, 255, 0.3);
border-radius: 3px;
}
.region-card .region-table .table-body::-webkit-scrollbar-thumb:hover {
background: rgba(0, 212, 255, 0.5);
}
.table-row {
padding: 18px 0; /* 增加上下内边距,提高行高 */
min-height: 60px; /* 提高最小高度,列表更舒展 */
border-bottom: 1px solid rgba(255,255,255,0.08);
color: #eaf7ff;
cursor: pointer;
transition: background-color 0.2s ease, box-shadow 0.2s ease, border-left-color 0.2s ease;
}
.table-row:nth-child(even) { background: rgba(0, 212, 255, 0.05); }
.table-row:nth-child(odd) { background: rgba(255, 255, 255, 0.02); }
.table-row:hover {
background: rgba(0, 212, 255, 0.12);
border-left: 2px solid #00d4ff;
}
.table-row.active {
background: rgba(0, 212, 255, 0.22);
border-left: 3px solid #00d4ff;
box-shadow: inset 0 0 12px rgba(0, 212, 255, 0.35), 0 0 8px rgba(0, 212, 255, 0.25);
color: #ffffff;
}
.table-row.active .td {
font-weight: 600;
}
/* 涨跌颜色(左侧列表也复用) */
.change-up { color: #00e676; font-weight: 600; }
.change-down { color: #ff5252; font-weight: 600; }
.th { font-size: 14px; }
.td { font-size: 14px; }
/* 仅将左侧“省内各地区牛数据列表”模块文字居中 */
.region-card .card-title { text-align: center; }
.region-card .region-table { text-align: center; }
.region-card .th, .region-card .td { text-align: center; }
.td .main { color: #eaf7ff; }
.td .sub { color: #9ec7d9; font-size: 12px; margin-top: 2px; }
.change-zero { color: #b9cdd8; }
.stats-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
}
.stat-item {
background: rgba(255,255,255,0.06);
border: 1px solid rgba(0,212,255,0.25);
border-radius: 8px;
padding: 10px;
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
}
.stat-item .label { color: #cfefff; font-size: 13px; }
.stat-item .value { color: #eaffff; font-size: 22px; font-weight: bold; }
.stat-item .unit { color: #9ed7ff; font-size: 12px; }
.trend-chart {
width: 100%;
flex: 1;
min-height: 0;
}
@media (max-width: 1366px) {
/* 小屏幕下可能需要调整 */
.trend-chart { height: auto; }
.breed-donut { height: auto; }
/* 保持6列布局但可能需要调整字体或间距 */
/* 如果太挤,可以改为第一行 3+3第二行 2+2+2 仍然适用,或者改为 2列布局 */
/* 这里暂时保持用户要求的布局 */
}
@media (max-width: 768px) {
.price-content { grid-template-columns: 1fr; }
.breed-donut { height: 220px; }
.trend-chart { height: 180px; }
.stats-bottom { grid-template-columns: 1fr; }
.table-header, .table-row { grid-template-columns: 0.6fr 0.9fr 1.2fr 1fr 0.9fr; }
.stats-grid { grid-template-columns: 1fr; }
.th, .td { font-size: 13px; }
}
/* 小高度屏幕下同步头部高度为 60px避免出现整体滚动 */
@media screen and (max-height: 768px) {
.price-page { height: calc(100vh - 60px); }
}
</style>