diff --git a/public/favicon.svg b/public/favicon.svg
index 763e9c2..7b5eb65 100644
--- a/public/favicon.svg
+++ b/public/favicon.svg
@@ -1,4 +1,4 @@
-
diff --git a/src/components/Home.vue b/src/components/Home.vue
index 9dba168..c148944 100644
--- a/src/components/Home.vue
+++ b/src/components/Home.vue
@@ -11,19 +11,18 @@
{{ idx + 1 }}
{{ row.province }}
+
{{ row.location }}
{{ row.breed }}
-
-
-
{{ formatPrice(row.price) }}
-
+
{{ formatPrice(row.price) }}
+
{{ formatDate(row.time) }}
@@ -237,6 +236,7 @@
padding: 10px;
height: 250px; /* 设置固定高度 */
overflow-y: auto;
+ position: relative;
}
.species-price-table-header,
.species-price-table-row {
@@ -249,8 +249,13 @@
.species-price-table-header {
color: #84acf0;
border-bottom: 1px solid rgba(132, 172, 240, 0.2);
- padding-bottom: 6px;
+ padding: 10px 0;
font-weight: bold;
+ position: sticky;
+ top: 0;
+ z-index: 10;
+ background: #0C2435;
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.35);
}
.species-price-table-row {
color: #eaf7ff;
@@ -267,24 +272,47 @@
flex: 1;
min-height: 0;
overflow-y: auto;
+ position: relative;
}
.price-table-header,
.price-table-row {
display: grid;
- grid-template-columns: 0.6fr 1fr 1.2fr 2fr;
- gap: 12px;
+ grid-template-columns: 0.6fr 1fr 1.2fr 1.2fr 1.4fr 0.9fr;
+ gap: 14px;
align-items: center;
- font-size: 14px;
+ font-size: 15px;
+}
+
+.price-table-header > div:nth-child(5),
+.price-table-row > div:nth-child(5) {
+ padding-left: 12px;
+}
+
+.price-table-header > div:nth-child(4),
+.price-table-row > div:nth-child(4) {
+ padding-right: 8px;
}
.price-table-header {
color: #84acf0;
border-bottom: 1px solid rgba(132, 172, 240, 0.2);
- padding-bottom: 6px;
+ padding: 8px 0;
font-weight: bold;
+ position: sticky;
+ top: 0;
+ z-index: 10;
+ background: #0C2435;
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.35);
}
.price-table-row {
color: #eaf7ff;
}
+/* 奇偶行颜色区分 */
+/* .price-table-row:nth-child(odd) {
+ background: rgba(255, 255, 255, 0.03);
+}
+.price-table-row:nth-child(even) {
+ background: rgba(0, 212, 255, 0.06);
+} */
/* 单价列条形图样式 */
.price-bar-cell {
display: flex;
@@ -309,13 +337,13 @@
}
.price-value {
min-width: 86px;
- text-align: right;
+ text-align: left;
color: #eaf7ff;
font-variant-numeric: tabular-nums;
}
.price-value.after-bar {
min-width: 86px;
- text-align: right;
+ text-align: left;
color: #eaf7ff;
font-weight: 600;
}
@@ -386,6 +414,15 @@
gap: 10px;
margin-bottom: 10px;
}
+.price-ranking-panel .panel-header {
+ margin-bottom: 0;
+}
+.price-ranking-panel .price-table {
+ padding-top: 0;
+}
+.price-ranking-panel .species-price-table {
+ padding-top: 0;
+}
.panel-header h3 {
color: #ffffff;
@@ -478,7 +515,7 @@ export default {
'新疆维吾尔自治区': '新疆',
'西藏自治区': '西藏',
'宁夏回族自治区': '宁夏',
- '广西自治区': '广西',
+ '广西壮族自治区': '广西',
'河北省': '河北',
'山东省': '山东',
'黑龙江省': '黑龙江',
@@ -776,16 +813,16 @@ export default {
// 全国牛单价排行榜表格数据
nationalPriceTableRows: [
- { id: 1, province: '河北省', breed: '安格斯牛', price: 14200 },
- { id: 2, province: '山东省', breed: '荷斯坦牛', price: 17000 },
- { id: 3, province: '江苏省', breed: '复洲黄牛', price: 14500 },
- { id: 4, province: '浙江省', breed: '西门塔尔牛', price: 16800 },
- { id: 5, province: '新疆维吾尔自治区', breed: '夏洛莱牛', price: 16500 },
- { id: 6, province: '甘肃省', breed: '水牛', price: 17387 },
- { id: 7, province: '广东省', breed: '安格斯牛', price: 14200 },
- { id: 8, province: '广西壮族自治区', breed: '荷斯坦牛', price: 17000 },
- { id: 9, province: '湖南省', breed: '复洲黄牛', price: 14500 },
- { id: 10, province: '河南省', breed: '西门塔尔牛', price: 16800 }
+ { id: 1, province: '河北省', location: '石家庄市', breed: '安格斯牛', price: 14200, time: '2025-12-03' },
+ { id: 2, province: '山东省', location: '济南市', breed: '荷斯坦牛', price: 17000, time: '2025-12-03' },
+ { id: 3, province: '江苏省', location: '南京市', breed: '复洲黄牛', price: 14500, time: '2025-12-03' },
+ { id: 4, province: '浙江省', location: '杭州市', breed: '西门塔尔牛', price: 16800, time: '2025-12-03' },
+ { id: 5, province: '新疆维吾尔自治区', location: '乌鲁木齐市', breed: '夏洛莱牛', price: 16500, time: '2025-12-03' },
+ { id: 6, province: '甘肃省', location: '兰州市', breed: '水牛', price: 17387, time: '2025-12-03' },
+ { id: 7, province: '广东省', location: '广州市', breed: '安格斯牛', price: 14200, time: '2025-12-03' },
+ { id: 8, province: '广西壮族自治区', location: '南宁市', breed: '荷斯坦牛', price: 17000, time: '2025-12-03' },
+ { id: 9, province: '湖南省', location: '长沙市', breed: '复洲黄牛', price: 14500, time: '2025-12-03' },
+ { id: 10, province: '河南省', location: '郑州市', breed: '西门塔尔牛', price: 16800, time: '2025-12-03' }
],
// 全国省份平均单价数据源(专用于右侧省份排行图表)
@@ -846,10 +883,11 @@ export default {
],
// 耳标统计数据
- earTagStats: {
- completed: 45678,
- planned: 52000
- },
+ earTagStats: {
+ completed: 45678,
+ planned: 52000
+ },
+ _provinceDailyTimer: null,
// 耳标佩戴统计堆叠柱状图配置
earTagChartOption: {
@@ -1079,9 +1117,9 @@ export default {
nameTextStyle: { color: '#00ffff', fontSize: 11 },
axisLine: { lineStyle: { color: '#00ffff' } },
axisLabel: { color: '#ffffff', fontSize: 10 },
- min: 12,
- max: 15,
- interval: 1,
+ min: 5,
+ max: 25,
+ interval: 5,
splitLine: { lineStyle: { color: 'rgba(0,255,255,0.2)' } }
},
series: [{
@@ -1147,6 +1185,19 @@ export default {
return String(value || 0)
}
},
+ formatDate(value) {
+ if (!value) {
+ const t = new Date()
+ const m = String(t.getMonth() + 1).padStart(2, '0')
+ const day = String(t.getDate()).padStart(2, '0')
+ return `${m}-${day}`
+ }
+ const d = new Date(value)
+ if (Number.isNaN(d.getTime())) return String(value)
+ const m = String(d.getMonth() + 1).padStart(2, '0')
+ const day = String(d.getDate()).padStart(2, '0')
+ return `${m}-${day}`
+ },
// 计算条形宽度样式
getPriceBarStyle(price) {
const max = this.priceBarMax || 1
@@ -1165,8 +1216,10 @@ export default {
const rows = list.map((item, idx) => ({
id: item.id ?? idx + 1,
province: item.province ?? item.provinceName ?? '',
+ location: item.location ?? '',
breed: item.type ?? item.breed ?? '',
- price: Number(item.price)
+ price: Number(item.price),
+ time: item.time ?? item.priceDate ?? item.date ?? ''
})).filter(r => r.province && r.breed && Number.isFinite(r.price))
// 更新数据源(驱动左侧表格与右侧省份排行图表)
this.nationalPriceTableRows = rows
@@ -1197,19 +1250,29 @@ export default {
// 拉取全国省份平均单价排行榜数据(字段: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)
+ const fmt = (d) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
+ const base = new Date()
+ let tryDate = fmt(base)
+ for (let i = 0; i < 7; i++) {
+ const url = `/api/cattle-data/province-daily-prices?priceDate=${encodeURIComponent(tryDate)}`
+
+ 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.price ?? item.provincePrice)
+ })).filter(r => r.province && Number.isFinite(r.price))
+ if (rows.length > 0) {
+ this.nationalProvinceAverageRows = rows
+ break
+ }
+ } catch (e) {}
+ const d = new Date(tryDate)
+ d.setDate(d.getDate() - 1)
+ tryDate = fmt(d)
}
},
@@ -1563,6 +1626,25 @@ export default {
this.fetchNationalPriceRanking()
// 拉取全国省份平均单价排行榜数据
this.fetchNationalProvinceAverages()
+ if (this._provinceDailyTimer) clearTimeout(this._provinceDailyTimer)
+ const schedule = () => {
+ const now = new Date()
+ const next = new Date(now)
+ next.setHours(12, 0, 0, 0)
+ if (now.getTime() >= next.getTime()) next.setDate(next.getDate() + 1)
+ const delay = next.getTime() - now.getTime()
+ this._provinceDailyTimer = setTimeout(async () => {
+ await this.fetchNationalProvinceAverages()
+ schedule()
+ }, Math.max(1000, delay))
+ }
+ schedule()
+ },
+ beforeUnmount() {
+ if (this._provinceDailyTimer) {
+ clearTimeout(this._provinceDailyTimer)
+ this._provinceDailyTimer = null
+ }
}
}
diff --git a/src/components/Map3D.vue b/src/components/Map3D.vue
index 50eb33d..508262a 100644
--- a/src/components/Map3D.vue
+++ b/src/components/Map3D.vue
@@ -65,6 +65,29 @@ export default {
{ province: '四川', count: 811 },
]);
+ const fetchCattleSourceTop3 = async () => {
+ try {
+ const url = '/api/cattle-data/provinces'
+ 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) => {
+ const province = item.province ?? item.provinceName ?? ''
+ // 接口字段单位为“万头”,无需再除以10000
+ const invRaw = item.inventory25th ?? item.inventory_25 ?? item.inventory2025 ?? item.inventory
+ const inv = Number(invRaw)
+ return { province, inv }
+ }).filter(x => x.province && Number.isFinite(x.inv) && x.inv > 0)
+ if (rows.length === 0) return
+ rows.sort((a, b) => b.inv - a.inv)
+ const top3 = rows.slice(0, 3).map(x => ({
+ province: x.province,
+ count: Math.round(x.inv)
+ }))
+ cattleSourceTop5.value = top3
+ } catch (e) {}
+ }
+
// 重置
const resize = () => {
baseEarth.resize();
@@ -713,6 +736,7 @@ export default {
};
};
onMounted(async () => {
+ fetchCattleSourceTop3()
console.log('=== Map3D组件已挂载 ===');
// console.log('farmData:', farmData); // 移除farmData引用
// 等待DOM完全渲染
diff --git a/src/utils/lodash/lodash.default.js b/src/utils/lodash/lodash.default.js
index c61abf9..08a2926 100644
--- a/src/utils/lodash/lodash.default.js
+++ b/src/utils/lodash/lodash.default.js
@@ -544,7 +544,7 @@ baseForOwn(LazyWrapper.prototype, function(func, methodName) {
var checkIteratee = /^(?:filter|find|map|reject)|While$/.test(methodName),
isTaker = /^(?:head|last)$/.test(methodName),
lodashFunc = lodash[isTaker ? ('take' + (methodName == 'last' ? 'Right' : '')) : methodName],
- retUnwrapped = isTaker || /^find/.test(methodName);
+ retUnwrapped = isTaker || methodName.startsWith('find');
if (!lodashFunc) {
return;