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

850 lines
28 KiB
Vue
Raw Normal View History

<script>
2025-12-01 16:52:54 +08:00
import { ref, computed, onMounted, watch } from 'vue'
import { use } from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
2025-12-01 16:52:54 +08:00
import { LineChart, PieChart, BarChart } from 'echarts/charts'
import { TitleComponent, TooltipComponent, GridComponent, LegendComponent } from 'echarts/components'
import VChart from 'vue-echarts'
2025-12-01 16:52:54 +08:00
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: ['本地黄牛', '西门塔尔牛']
}
2025-12-01 16:52:54 +08:00
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)' }
}]
}))
2025-12-01 16:52:54 +08:00
// 品种占比统计(环形图)
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 }))
2025-12-02 17:27:45 +08:00
// 指定配色:#FF7A28#00E9FF#4E73DF#0B1424
const brandColors = ['#FF7A28', '#00E9FF', '#4E73DF', '#0B1424','#61eaff']
2025-12-01 16:52:54 +08:00
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)
}
2025-12-01 16:52:54 +08:00
// 年度统计数据(示例)
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(() => ({
2025-12-02 17:27:45 +08:00
title: { text: '存栏统计(万头)', left: 'center', textStyle: { color: '#eaf7ff' } },
2025-12-01 16:52:54 +08:00
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(() => ({
2025-12-02 17:27:45 +08:00
title: { text: '出栏统计(万头)', left: 'center', textStyle: { color: '#eaf7ff' } },
2025-12-01 16:52:54 +08:00
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 ? '↑' : '↓'
})
2025-12-01 16:52:54 +08:00
// 统一在选中省份变化时加载数据,并在初始化时立即执行一次
watch(() => props.selectedProvince, async (newVal, oldVal) => {
if (newVal === oldVal) return
selectedRow.value = null
const ok = await fetchProvinceRegionRows()
if (!ok) genRegionRows()
genData()
2025-12-01 16:52:54 +08:00
genStats()
}, { immediate: true })
2025-12-01 16:52:54 +08:00
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">
2025-12-01 16:52:54 +08:00
<!-- 第一行左侧牛数据列表 + 右侧价格详情等高 -->
<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">
2025-12-02 17:27:45 +08:00
<div class="th">序号</div>
2025-12-01 16:52:54 +08:00
<div class="th">时间</div>
<div class="th">地区</div>
<div class="th">品类</div>
<div class="th">价格{{ unit }}</div>
</div>
2025-12-01 16:52:54 +08:00
<div class="table-body">
2025-12-02 17:27:45 +08:00
<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>
2025-12-01 16:52:54 +08:00
<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>
2025-12-01 16:52:54 +08:00
</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">
2025-12-02 17:27:45 +08:00
<!-- <div class="today-price-line">
2025-12-01 16:52:54 +08:00
<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>
2025-12-01 16:52:54 +08:00
<div class="price-display">
<span class="currency">¥</span>
<span class="value">{{ avgToday.toFixed(2) }}</span>
<span class="unit">{{ unit }}</span>
</div>
2025-12-02 17:27:45 +08:00
</div> -->
2025-12-02 17:27:45 +08:00
<!-- <div class="filters">
2025-12-01 16:52:54 +08:00
<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>
2025-12-02 17:27:45 +08:00
</div> -->
2025-12-01 16:52:54 +08:00
<v-chart class="trend-chart" :option="chartOption" autoresize />
</div>
2025-12-01 16:52:54 +08:00
</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%;
2025-12-02 17:27:45 +08:00
height: calc(100vh - 100px);
padding: 16px 24px;
display: flex;
flex-direction: column;
gap: 16px;
background: #011819; /* 与预警监测页背景一致 */
position: relative;
2025-12-01 16:52:54 +08:00
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;
2025-12-01 16:52:54 +08:00
grid-template-columns: repeat(6, 1fr); /* 6列方便分配 3:3 和 2:2:2 */
grid-template-rows: 1fr 1fr; /* 两行等高 */
gap: 16px;
2025-12-01 16:52:54 +08:00
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;
2025-12-01 16:52:54 +08:00
height: 100%; /* 与所在网格行高度一致 */
display: flex;
flex-direction: column; /* 让内部内容自适应高度 */
}
2025-12-01 16:52:54 +08:00
/* 网格跨度设置 */
.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;
}
2025-12-01 16:52:54 +08:00
/* 移除旧的 .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;
2025-12-01 16:52:54 +08:00
padding: 8px 10px; /* 缩小内边距,降低模块总高度 */
}
.detail-content {
display: flex;
flex-direction: column;
2025-12-01 16:52:54 +08:00
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;
2025-12-02 17:27:45 +08:00
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;
}
2025-12-01 16:52:54 +08:00
/* 缩小左侧“省内各地区牛数据列表”模块高度 - 移除固定高度 */
.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; }
2025-12-01 16:52:54 +08:00
.trend-chart {
width: 100%;
flex: 1;
min-height: 0;
}
@media (max-width: 1366px) {
2025-12-01 16:52:54 +08:00
/* 小屏幕下可能需要调整 */
.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; }
2025-12-01 16:52:54 +08:00
.breed-donut { height: 220px; }
.trend-chart { height: 180px; }
.stats-bottom { grid-template-columns: 1fr; }
2025-12-02 17:27:45 +08:00
.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; }
}
2025-12-01 16:52:54 +08:00
2025-12-02 17:27:45 +08:00
/* 小高度屏幕下同步头部高度为 60px避免出现整体滚动 */
@media screen and (max-height: 768px) {
.price-page { height: calc(100vh - 60px); }
}
</style>