parent
7d815519b9
commit
b39ecb74e5
@ -0,0 +1,42 @@ |
|||||||
|
# 数据配置使用指南 |
||||||
|
|
||||||
|
## 概述 |
||||||
|
本系统支持通过JSON配置文件来管理所有模块的数据,方便在部署时进行数据修改而无需重新编译代码。 |
||||||
|
|
||||||
|
## 配置文件位置 |
||||||
|
- **开发环境**: `public/data-config.json` |
||||||
|
- **生产环境**: 部署后的 `/data-config.json` 文件 |
||||||
|
|
||||||
|
## 主要数据模块 |
||||||
|
|
||||||
|
### 1. 实时交易统计 (realTimeStats) |
||||||
|
- todayTransactions: 今日交易笔数 |
||||||
|
- totalAmount: 交易总额(万元) |
||||||
|
- activeUsers: 活跃用户数 |
||||||
|
- systemStatus: 系统状态 |
||||||
|
|
||||||
|
### 2. 牦牛交易数据 (yakTradingData) |
||||||
|
- totalCount: 总交易数量 |
||||||
|
- avgPrice: 平均价格 |
||||||
|
- monthlyGrowth: 月增长率(%) |
||||||
|
- topRegion: 主要地区 |
||||||
|
|
||||||
|
## 部署时修改数据 |
||||||
|
|
||||||
|
### 方法一:直接修改JSON文件 |
||||||
|
1. 找到部署目录下的 `data-config.json` 文件 |
||||||
|
2. 使用文本编辑器打开文件 |
||||||
|
3. 修改对应的数据值 |
||||||
|
4. 保存文件 |
||||||
|
5. 刷新浏览器页面即可看到更新 |
||||||
|
|
||||||
|
### 方法二:替换整个配置文件 |
||||||
|
1. 准备新的 `data-config.json` 文件 |
||||||
|
2. 替换部署目录下的原文件 |
||||||
|
3. 刷新浏览器页面 |
||||||
|
|
||||||
|
## 注意事项 |
||||||
|
1. **JSON格式**: 确保修改后的文件符合JSON格式规范 |
||||||
|
2. **数据类型**: 注意保持数据类型一致(数字、字符串、数组等) |
||||||
|
3. **编码格式**: 文件应保存为UTF-8编码 |
||||||
|
4. **备份**: 修改前建议备份原文件 |
||||||
@ -0,0 +1,26 @@ |
|||||||
|
<!DOCTYPE html> |
||||||
|
<html lang="zh-CN"> |
||||||
|
<head> |
||||||
|
<meta charset="UTF-8" /> |
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> |
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
||||||
|
<title>智慧活畜交易大数据一张图</title> |
||||||
|
<style> |
||||||
|
* { |
||||||
|
margin: 0; |
||||||
|
padding: 0; |
||||||
|
box-sizing: border-box; |
||||||
|
} |
||||||
|
|
||||||
|
body { |
||||||
|
font-family: 'Microsoft YaHei', Arial, sans-serif; |
||||||
|
background-color: #0a0e1a; |
||||||
|
overflow: hidden; |
||||||
|
} |
||||||
|
</style> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<div id="app"></div> |
||||||
|
<script type="module" src="/src/main.js"></script> |
||||||
|
</body> |
||||||
|
</html> |
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,22 @@ |
|||||||
|
{ |
||||||
|
"name": "livestock-trading-dashboard", |
||||||
|
"version": "1.0.0", |
||||||
|
"description": "智慧活畜交易大数据一张图", |
||||||
|
"main": "index.js", |
||||||
|
"scripts": { |
||||||
|
"dev": "vite", |
||||||
|
"build": "vite build", |
||||||
|
"preview": "vite preview" |
||||||
|
}, |
||||||
|
"dependencies": { |
||||||
|
"vue": "^3.3.4", |
||||||
|
"echarts": "^5.4.3", |
||||||
|
"axios": "^1.5.0", |
||||||
|
"@vueuse/core": "^10.4.1" |
||||||
|
}, |
||||||
|
"devDependencies": { |
||||||
|
"@vitejs/plugin-vue": "^4.4.0", |
||||||
|
"vite": "^4.4.9", |
||||||
|
"sass": "^1.66.1" |
||||||
|
} |
||||||
|
} |
||||||
File diff suppressed because one or more lines are too long
@ -0,0 +1,11 @@ |
|||||||
|
{ |
||||||
|
"screen": { |
||||||
|
"width": 5120, |
||||||
|
"height": 1440, |
||||||
|
"title": "红原县智慧活畜交易大数据平台" |
||||||
|
}, |
||||||
|
"dashboard": { |
||||||
|
"refreshInterval": 5000, |
||||||
|
"animationDuration": 300 |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,160 @@ |
|||||||
|
{ |
||||||
|
"realTimeStats": { |
||||||
|
"yakTotalVolume": 15420, |
||||||
|
"orderTotalVolume": 1248, |
||||||
|
"sellerCount": 156, |
||||||
|
"buyerCount": 89, |
||||||
|
"systemStatus": "正常运行" |
||||||
|
}, |
||||||
|
"yakTradingData": { |
||||||
|
"totalCount": 15420, |
||||||
|
"avgPrice": 8500, |
||||||
|
"monthlyGrowth": 12.5, |
||||||
|
"topRegion": "西藏" |
||||||
|
}, |
||||||
|
"comprehensiveSalesStats": { |
||||||
|
"localCountySales": [ |
||||||
|
{ "name": "A县", "value": 1240 }, |
||||||
|
{ "name": "B县", "value": 890 }, |
||||||
|
{ "name": "C县", "value": 560 }, |
||||||
|
{ "name": "D县", "value": 450 }, |
||||||
|
{ "name": "E县", "value": 320 }, |
||||||
|
{ "name": "F县", "value": 240 } |
||||||
|
], |
||||||
|
"overallSalesDistribution": [ |
||||||
|
{ "name": "A市", "value": 2340 }, |
||||||
|
{ "name": "B市", "value": 1890 }, |
||||||
|
{ "name": "C市", "value": 1456 }, |
||||||
|
{ "name": "D市", "value": 1234 }, |
||||||
|
{ "name": "E市", "value": 987 }, |
||||||
|
{ "name": "F市", "value": 289 } |
||||||
|
], |
||||||
|
"purchaseRegionDistribution": [ |
||||||
|
{ "name": "A省", "value": 1560 }, |
||||||
|
{ "name": "B省", "value": 1230 }, |
||||||
|
{ "name": "C省", "value": 980 }, |
||||||
|
{ "name": "D省", "value": 750 }, |
||||||
|
{ "name": "E省", "value": 620 }, |
||||||
|
{ "name": "F省", "value": 460 } |
||||||
|
] |
||||||
|
}, |
||||||
|
"yakPriceTrend": { |
||||||
|
"months": ["1月", "2月", "3月", "4月", "5月", "6月"], |
||||||
|
"prices": [7800, 8200, 8500, 8300, 8600, 8500], |
||||||
|
"volumes": [1200, 1350, 1480, 1420, 1560, 1520] |
||||||
|
}, |
||||||
|
"chinaMapData": [ |
||||||
|
{ "name": "西藏", "value": 8520 }, |
||||||
|
{ "name": "青海", "value": 3240 }, |
||||||
|
{ "name": "四川", "value": 2180 }, |
||||||
|
{ "name": "云南", "value": 1560 }, |
||||||
|
{ "name": "甘肃", "value": 890 } |
||||||
|
], |
||||||
|
"exchangeMonitor": { |
||||||
|
"onlineUsers": 156, |
||||||
|
"activeTransactions": 23, |
||||||
|
"systemLoad": 68, |
||||||
|
"networkStatus": "良好" |
||||||
|
}, |
||||||
|
"yakSalesTypeStats": [ |
||||||
|
{ "name": "成年牦牛", "value": 45.2, "count": 6780 }, |
||||||
|
{ "name": "幼牛", "value": 28.6, "count": 4290 }, |
||||||
|
{ "name": "母牛", "value": 18.9, "count": 2835 }, |
||||||
|
{ "name": "种牛", "value": 7.3, "count": 1095 } |
||||||
|
], |
||||||
|
"purchaserAnalysis": [ |
||||||
|
{ "type": "个人买家", "count": 1245, "percentage": 62.3 }, |
||||||
|
{ "type": "企业采购", "count": 456, "percentage": 22.8 }, |
||||||
|
{ "type": "合作社", "count": 234, "percentage": 11.7 }, |
||||||
|
{ "type": "其他", "count": 65, "percentage": 3.2 } |
||||||
|
], |
||||||
|
"marketEnvironment": { |
||||||
|
"temperature": "15°C", |
||||||
|
"humidity": "45%", |
||||||
|
"airQuality": "优", |
||||||
|
"weather": "晴" |
||||||
|
}, |
||||||
|
"marketRealtime": { |
||||||
|
"currentPrice": 8500, |
||||||
|
"priceChange": "+2.3%", |
||||||
|
"volume": 1520, |
||||||
|
"volumeChange": "-1.2%", |
||||||
|
"lastUpdate": "2024-01-15 14:30:00" |
||||||
|
}, |
||||||
|
"supplyDemandData": { |
||||||
|
"supply": { |
||||||
|
"total": 15420, |
||||||
|
"available": 12340, |
||||||
|
"reserved": 3080 |
||||||
|
}, |
||||||
|
"demand": { |
||||||
|
"total": 18650, |
||||||
|
"pending": 6230, |
||||||
|
"matched": 12420 |
||||||
|
}, |
||||||
|
"ratio": 0.83 |
||||||
|
}, |
||||||
|
"transactionDetails": [ |
||||||
|
{ |
||||||
|
"id": "TX20240115001", |
||||||
|
"buyer": "张三", |
||||||
|
"seller": "李四", |
||||||
|
"type": "成年牦牛", |
||||||
|
"quantity": 5, |
||||||
|
"price": 8500, |
||||||
|
"total": 42500, |
||||||
|
"status": "已完成", |
||||||
|
"time": "14:25" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": "TX20240115002", |
||||||
|
"buyer": "王五", |
||||||
|
"seller": "赵六", |
||||||
|
"type": "幼牛", |
||||||
|
"quantity": 8, |
||||||
|
"price": 6200, |
||||||
|
"total": 49600, |
||||||
|
"status": "进行中", |
||||||
|
"time": "14:20" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": "TX20240115003", |
||||||
|
"buyer": "陈七", |
||||||
|
"seller": "刘八", |
||||||
|
"type": "母牛", |
||||||
|
"quantity": 3, |
||||||
|
"price": 9200, |
||||||
|
"total": 27600, |
||||||
|
"status": "已完成", |
||||||
|
"time": "14:15" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": "TX20240115004", |
||||||
|
"buyer": "孙九", |
||||||
|
"seller": "周十", |
||||||
|
"type": "种牛", |
||||||
|
"quantity": 2, |
||||||
|
"price": 12000, |
||||||
|
"total": 24000, |
||||||
|
"status": "待确认", |
||||||
|
"time": "14:10" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": "TX20240115005", |
||||||
|
"buyer": "吴一", |
||||||
|
"seller": "郑二", |
||||||
|
"type": "成年牦牛", |
||||||
|
"quantity": 6, |
||||||
|
"price": 8300, |
||||||
|
"total": 49800, |
||||||
|
"status": "已完成", |
||||||
|
"time": "14:05" |
||||||
|
} |
||||||
|
], |
||||||
|
"announcements": [ |
||||||
|
"【重要通知】系统将于今晚22:00-23:00进行维护升级", |
||||||
|
"【市场动态】本周牦牛价格稳中有升,建议关注市场变化", |
||||||
|
"【政策解读】新的活畜交易规范将于下月实施", |
||||||
|
"【技术支持】如遇交易问题请及时联系客服热线400-123-4567" |
||||||
|
] |
||||||
|
} |
||||||
File diff suppressed because one or more lines are too long
@ -0,0 +1,471 @@ |
|||||||
|
[ |
||||||
|
{ |
||||||
|
"id": 1, |
||||||
|
"name": "扎西多杰", |
||||||
|
"licensePlate": "川A88888", |
||||||
|
"yakCount": 25, |
||||||
|
"contact": "138****5678", |
||||||
|
"origin": "红原县安曲镇", |
||||||
|
"quarantineNo": "HY2024001", |
||||||
|
"entryTime": "2024-01-15 08: 30", |
||||||
|
"progress": 80, |
||||||
|
"tradedCount": 20, |
||||||
|
"pendingCount": 5, |
||||||
|
"entryPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjMmEyYTJhIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzY2NiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPuWFpeWcuueXp+eJhzwvdGV4dD48L3N2Zz4=", |
||||||
|
"quarantineCert": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjMmEyYTJhIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzY2NiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPuajgOeWq+ivgTwvdGV4dD48L3N2Zz4=", |
||||||
|
"orders": [ |
||||||
|
{ |
||||||
|
"id": 1, |
||||||
|
"buyer": "成都肉业公司", |
||||||
|
"seller": "扎西多杰", |
||||||
|
"tradeTime": "2024-01-15 10: 20", |
||||||
|
"quantity": 12, |
||||||
|
"weight": 1680, |
||||||
|
"weightPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iIzJhMmEyYSIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTAiIGZpbGw9IiM2NjYiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIj7np7Dpho08L3RleHQ+PC9zdmc+" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 2, |
||||||
|
"buyer": "重庆牧业", |
||||||
|
"seller": "扎西多杰", |
||||||
|
"tradeTime": "2024-01-15 14: 15", |
||||||
|
"quantity": 8, |
||||||
|
"weight": 1120, |
||||||
|
"weightPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iIzJhMmEyYSIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTAiIGZpbGw9IiM2NjYiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIj7np7Dpho08L3RleHQ+PC9zdmc+" |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 2, |
||||||
|
"name": "达娃次仁", |
||||||
|
"licensePlate": "川A99999", |
||||||
|
"yakCount": 18, |
||||||
|
"contact": "139****1234", |
||||||
|
"origin": "红原县瓦切镇", |
||||||
|
"quarantineNo": "HY2024002", |
||||||
|
"entryTime": "2024-01-15 09: 15", |
||||||
|
"progress": 50, |
||||||
|
"tradedCount": 9, |
||||||
|
"pendingCount": 9, |
||||||
|
"entryPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjMmEyYTJhIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzY2NiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPuWFpeWcuueXp+eJhzwvdGV4dD48L3N2Zz4=", |
||||||
|
"quarantineCert": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjMmEyYTJhIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzY2NiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPuajgOeWq+ivgTwvdGV4dD48L3N2Zz4=", |
||||||
|
"orders": [ |
||||||
|
{ |
||||||
|
"id": 3, |
||||||
|
"buyer": "昆明食品厂", |
||||||
|
"seller": "达娃次仁", |
||||||
|
"tradeTime": "2024-01-15 11: 30", |
||||||
|
"quantity": 9, |
||||||
|
"weight": 1260, |
||||||
|
"weightPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iIzJhMmEyYSIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTAiIGZpbGw9IiM2NjYiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIj7np7Dpho08L3RleHQ+PC9zdmc+" |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 3, |
||||||
|
"name": "洛桑平措", |
||||||
|
"licensePlate": "川A77777", |
||||||
|
"yakCount": 32, |
||||||
|
"contact": "137****9876", |
||||||
|
"origin": "红原县刷经寺镇", |
||||||
|
"quarantineNo": "HY2024003", |
||||||
|
"entryTime": "2024-01-15 07: 45", |
||||||
|
"progress": 100, |
||||||
|
"tradedCount": 32, |
||||||
|
"pendingCount": 0, |
||||||
|
"entryPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjMmEyYTJhIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzY2NiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPuWFpeWcuueXp+eJhzwvdGV4dD48L3N2Zz4=", |
||||||
|
"quarantineCert": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjMmEyYTJhIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzY2NiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPuajgOeWq+ivgTwvdGV4dD48L3N2Zz4=", |
||||||
|
"orders": [ |
||||||
|
{ |
||||||
|
"id": 4, |
||||||
|
"buyer": "西安牧业集团", |
||||||
|
"seller": "洛桑平措", |
||||||
|
"tradeTime": "2024-01-15 09: 00", |
||||||
|
"quantity": 20, |
||||||
|
"weight": 2800, |
||||||
|
"weightPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iIzJhMmEyYSIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTAiIGZpbGw9IiM2NjYiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIj7np7Dpho08L3RleHQ+PC9zdmc+" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 5, |
||||||
|
"buyer": "兰州畜牧公司", |
||||||
|
"seller": "洛桑平措", |
||||||
|
"tradeTime": "2024-01-15 13: 45", |
||||||
|
"quantity": 12, |
||||||
|
"weight": 1680, |
||||||
|
"weightPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iIzJhMmEyYSIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTAiIGZpbGw9IiM2NjYiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIj7np7Dpho08L3RleHQ+PC9zdmc+" |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 4, |
||||||
|
"name": "白玛旺堆", |
||||||
|
"licensePlate": "川A66666", |
||||||
|
"yakCount": 15, |
||||||
|
"contact": "136****5432", |
||||||
|
"origin": "红原县江茸乡", |
||||||
|
"quarantineNo": "HY2024004", |
||||||
|
"entryTime": "2024-01-15 10: 20", |
||||||
|
"progress": 30, |
||||||
|
"tradedCount": 4, |
||||||
|
"pendingCount": 11, |
||||||
|
"entryPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjMmEyYTJhIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzY2NiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPuWFpeWcuueXp+eJhzwvdGV4dD48L3N2Zz4=", |
||||||
|
"quarantineCert": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjMmEyYTJhIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzY2NiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPuajgOeWq+ivgTwvdGV4dD48L3N2Zz4=", |
||||||
|
"orders": [ |
||||||
|
{ |
||||||
|
"id": 6, |
||||||
|
"buyer": "贵阳食品公司", |
||||||
|
"tradeTime": "2024-01-15 12: 10", |
||||||
|
"quantity": 4, |
||||||
|
"weight": 560, |
||||||
|
"weightPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iIzJhMmEyYSIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTAiIGZpbGw9IiM2NjYiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIj7np7Dpho08L3RleHQ+PC9zdmc+" |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 1, |
||||||
|
"name": "扎西多杰", |
||||||
|
"licensePlate": "川A88888", |
||||||
|
"yakCount": 25, |
||||||
|
"contact": "138****5678", |
||||||
|
"origin": "红原县安曲镇", |
||||||
|
"quarantineNo": "HY2024001", |
||||||
|
"entryTime": "2024-01-15 08: 30", |
||||||
|
"progress": 80, |
||||||
|
"tradedCount": 20, |
||||||
|
"pendingCount": 5, |
||||||
|
"entryPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjMmEyYTJhIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzY2NiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPuWFpeWcuueXp+eJhzwvdGV4dD48L3N2Zz4=", |
||||||
|
"quarantineCert": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjMmEyYTJhIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzY2NiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPuajgOeWq+ivgTwvdGV4dD48L3N2Zz4=", |
||||||
|
"orders": [ |
||||||
|
{ |
||||||
|
"id": 1, |
||||||
|
"buyer": "成都肉业公司", |
||||||
|
"tradeTime": "2024-01-15 10: 20", |
||||||
|
"quantity": 12, |
||||||
|
"weight": 1680, |
||||||
|
"weightPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iIzJhMmEyYSIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTAiIGZpbGw9IiM2NjYiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIj7np7Dpho08L3RleHQ+PC9zdmc+" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 2, |
||||||
|
"buyer": "重庆牧业", |
||||||
|
"tradeTime": "2024-01-15 14: 15", |
||||||
|
"quantity": 8, |
||||||
|
"weight": 1120, |
||||||
|
"weightPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iIzJhMmEyYSIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTAiIGZpbGw9IiM2NjYiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIj7np7Dpho08L3RleHQ+PC9zdmc+" |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 2, |
||||||
|
"name": "达娃次仁", |
||||||
|
"licensePlate": "川A99999", |
||||||
|
"yakCount": 18, |
||||||
|
"contact": "139****1234", |
||||||
|
"origin": "红原县瓦切镇", |
||||||
|
"quarantineNo": "HY2024002", |
||||||
|
"entryTime": "2024-01-15 09: 15", |
||||||
|
"progress": 50, |
||||||
|
"tradedCount": 9, |
||||||
|
"pendingCount": 9, |
||||||
|
"entryPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjMmEyYTJhIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzY2NiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPuWFpeWcuueXp+eJhzwvdGV4dD48L3N2Zz4=", |
||||||
|
"quarantineCert": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjMmEyYTJhIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzY2NiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPuajgOeWq+ivgTwvdGV4dD48L3N2Zz4=", |
||||||
|
"orders": [ |
||||||
|
{ |
||||||
|
"id": 3, |
||||||
|
"buyer": "昆明食品厂", |
||||||
|
"tradeTime": "2024-01-15 11: 30", |
||||||
|
"quantity": 9, |
||||||
|
"weight": 1260, |
||||||
|
"weightPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iIzJhMmEyYSIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTAiIGZpbGw9IiM2NjYiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIj7np7Dpho08L3RleHQ+PC9zdmc+" |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 3, |
||||||
|
"name": "洛桑平措", |
||||||
|
"licensePlate": "川A77777", |
||||||
|
"yakCount": 32, |
||||||
|
"contact": "137****9876", |
||||||
|
"origin": "红原县刷经寺镇", |
||||||
|
"quarantineNo": "HY2024003", |
||||||
|
"entryTime": "2024-01-15 07: 45", |
||||||
|
"progress": 100, |
||||||
|
"tradedCount": 32, |
||||||
|
"pendingCount": 0, |
||||||
|
"entryPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjMmEyYTJhIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzY2NiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPuWFpeWcuueXp+eJhzwvdGV4dD48L3N2Zz4=", |
||||||
|
"quarantineCert": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjMmEyYTJhIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzY2NiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPuajgOeWq+ivgTwvdGV4dD48L3N2Zz4=", |
||||||
|
"orders": [ |
||||||
|
{ |
||||||
|
"id": 4, |
||||||
|
"buyer": "西安牧业集团", |
||||||
|
"tradeTime": "2024-01-15 09: 00", |
||||||
|
"quantity": 20, |
||||||
|
"weight": 2800, |
||||||
|
"weightPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iIzJhMmEyYSIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTAiIGZpbGw9IiM2NjYiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIj7np7Dpho08L3RleHQ+PC9zdmc+" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 5, |
||||||
|
"buyer": "兰州畜牧公司", |
||||||
|
"tradeTime": "2024-01-15 13: 45", |
||||||
|
"quantity": 12, |
||||||
|
"weight": 1680, |
||||||
|
"weightPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iIzJhMmEyYSIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTAiIGZpbGw9IiM2NjYiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIj7np7Dpho08L3RleHQ+PC9zdmc+" |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 4, |
||||||
|
"name": "白玛旺堆", |
||||||
|
"licensePlate": "川A66666", |
||||||
|
"yakCount": 15, |
||||||
|
"contact": "136****5432", |
||||||
|
"origin": "红原县江茸乡", |
||||||
|
"quarantineNo": "HY2024004", |
||||||
|
"entryTime": "2024-01-15 10: 20", |
||||||
|
"progress": 30, |
||||||
|
"tradedCount": 4, |
||||||
|
"pendingCount": 11, |
||||||
|
"entryPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjMmEyYTJhIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzY2NiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPuWFpeWcuueXp+eJhzwvdGV4dD48L3N2Zz4=", |
||||||
|
"quarantineCert": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjMmEyYTJhIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzY2NiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPuajgOeWq+ivgTwvdGV4dD48L3N2Zz4=", |
||||||
|
"orders": [ |
||||||
|
{ |
||||||
|
"id": 6, |
||||||
|
"buyer": "贵阳食品公司", |
||||||
|
"tradeTime": "2024-01-15 12: 10", |
||||||
|
"quantity": 4, |
||||||
|
"weight": 560, |
||||||
|
"weightPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iIzJhMmEyYSIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTAiIGZpbGw9IiM2NjYiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIj7np7Dpho08L3RleHQ+PC9zdmc+" |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 1, |
||||||
|
"name": "扎西多杰", |
||||||
|
"licensePlate": "川A88888", |
||||||
|
"yakCount": 25, |
||||||
|
"contact": "138****5678", |
||||||
|
"origin": "红原县安曲镇", |
||||||
|
"quarantineNo": "HY2024001", |
||||||
|
"entryTime": "2024-01-15 08: 30", |
||||||
|
"progress": 80, |
||||||
|
"tradedCount": 20, |
||||||
|
"pendingCount": 5, |
||||||
|
"entryPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjMmEyYTJhIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzY2NiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPuWFpeWcuueXp+eJhzwvdGV4dD48L3N2Zz4=", |
||||||
|
"quarantineCert": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjMmEyYTJhIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzY2NiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPuajgOeWq+ivgTwvdGV4dD48L3N2Zz4=", |
||||||
|
"orders": [ |
||||||
|
{ |
||||||
|
"id": 1, |
||||||
|
"buyer": "成都肉业公司", |
||||||
|
"tradeTime": "2024-01-15 10: 20", |
||||||
|
"quantity": 12, |
||||||
|
"weight": 1680, |
||||||
|
"weightPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iIzJhMmEyYSIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTAiIGZpbGw9IiM2NjYiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIj7np7Dpho08L3RleHQ+PC9zdmc+" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 2, |
||||||
|
"buyer": "重庆牧业", |
||||||
|
"tradeTime": "2024-01-15 14: 15", |
||||||
|
"quantity": 8, |
||||||
|
"weight": 1120, |
||||||
|
"weightPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iIzJhMmEyYSIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTAiIGZpbGw9IiM2NjYiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIj7np7Dpho08L3RleHQ+PC9zdmc+" |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 2, |
||||||
|
"name": "达娃次仁", |
||||||
|
"licensePlate": "川A99999", |
||||||
|
"yakCount": 18, |
||||||
|
"contact": "139****1234", |
||||||
|
"origin": "红原县瓦切镇", |
||||||
|
"quarantineNo": "HY2024002", |
||||||
|
"entryTime": "2024-01-15 09: 15", |
||||||
|
"progress": 50, |
||||||
|
"tradedCount": 9, |
||||||
|
"pendingCount": 9, |
||||||
|
"entryPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjMmEyYTJhIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzY2NiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPuWFpeWcuueXp+eJhzwvdGV4dD48L3N2Zz4=", |
||||||
|
"quarantineCert": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjMmEyYTJhIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzY2NiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPuajgOeWq+ivgTwvdGV4dD48L3N2Zz4=", |
||||||
|
"orders": [ |
||||||
|
{ |
||||||
|
"id": 3, |
||||||
|
"buyer": "昆明食品厂", |
||||||
|
"tradeTime": "2024-01-15 11: 30", |
||||||
|
"quantity": 9, |
||||||
|
"weight": 1260, |
||||||
|
"weightPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iIzJhMmEyYSIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTAiIGZpbGw9IiM2NjYiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIj7np7Dpho08L3RleHQ+PC9zdmc+" |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 3, |
||||||
|
"name": "洛桑平措", |
||||||
|
"licensePlate": "川A77777", |
||||||
|
"yakCount": 32, |
||||||
|
"contact": "137****9876", |
||||||
|
"origin": "红原县刷经寺镇", |
||||||
|
"quarantineNo": "HY2024003", |
||||||
|
"entryTime": "2024-01-15 07: 45", |
||||||
|
"progress": 100, |
||||||
|
"tradedCount": 32, |
||||||
|
"pendingCount": 0, |
||||||
|
"entryPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjMmEyYTJhIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzY2NiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPuWFpeWcuueXp+eJhzwvdGV4dD48L3N2Zz4=", |
||||||
|
"quarantineCert": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjMmEyYTJhIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzY2NiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPuajgOeWq+ivgTwvdGV4dD48L3N2Zz4=", |
||||||
|
"orders": [ |
||||||
|
{ |
||||||
|
"id": 4, |
||||||
|
"buyer": "西安牧业集团", |
||||||
|
"tradeTime": "2024-01-15 09: 00", |
||||||
|
"quantity": 20, |
||||||
|
"weight": 2800, |
||||||
|
"weightPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iIzJhMmEyYSIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTAiIGZpbGw9IiM2NjYiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIj7np7Dpho08L3RleHQ+PC9zdmc+" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 5, |
||||||
|
"buyer": "兰州畜牧公司", |
||||||
|
"tradeTime": "2024-01-15 13: 45", |
||||||
|
"quantity": 12, |
||||||
|
"weight": 1680, |
||||||
|
"weightPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iIzJhMmEyYSIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTAiIGZpbGw9IiM2NjYiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIj7np7Dpho08L3RleHQ+PC9zdmc+" |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 4, |
||||||
|
"name": "白玛旺堆", |
||||||
|
"licensePlate": "川A66666", |
||||||
|
"yakCount": 15, |
||||||
|
"contact": "136****5432", |
||||||
|
"origin": "红原县江茸乡", |
||||||
|
"quarantineNo": "HY2024004", |
||||||
|
"entryTime": "2024-01-15 10: 20", |
||||||
|
"progress": 30, |
||||||
|
"tradedCount": 4, |
||||||
|
"pendingCount": 11, |
||||||
|
"entryPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjMmEyYTJhIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzY2NiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPuWFpeWcuueXp+eJhzwvdGV4dD48L3N2Zz4=", |
||||||
|
"quarantineCert": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjMmEyYTJhIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzY2NiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPuajgOeWq+ivgTwvdGV4dD48L3N2Zz4=", |
||||||
|
"orders": [ |
||||||
|
{ |
||||||
|
"id": 6, |
||||||
|
"buyer": "贵阳食品公司", |
||||||
|
"tradeTime": "2024-01-15 12: 10", |
||||||
|
"quantity": 4, |
||||||
|
"weight": 560, |
||||||
|
"weightPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iIzJhMmEyYSIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTAiIGZpbGw9IiM2NjYiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIj7np7Dpho08L3RleHQ+PC9zdmc+" |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 1, |
||||||
|
"name": "扎西多杰", |
||||||
|
"licensePlate": "川A88888", |
||||||
|
"yakCount": 25, |
||||||
|
"contact": "138****5678", |
||||||
|
"origin": "红原县安曲镇", |
||||||
|
"quarantineNo": "HY2024001", |
||||||
|
"entryTime": "2024-01-15 08: 30", |
||||||
|
"progress": 80, |
||||||
|
"tradedCount": 20, |
||||||
|
"pendingCount": 5, |
||||||
|
"entryPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjMmEyYTJhIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzY2NiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPuWFpeWcuueXp+eJhzwvdGV4dD48L3N2Zz4=", |
||||||
|
"quarantineCert": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjMmEyYTJhIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzY2NiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPuajgOeWq+ivgTwvdGV4dD48L3N2Zz4=", |
||||||
|
"orders": [ |
||||||
|
{ |
||||||
|
"id": 1, |
||||||
|
"buyer": "成都肉业公司", |
||||||
|
"tradeTime": "2024-01-15 10: 20", |
||||||
|
"quantity": 12, |
||||||
|
"weight": 1680, |
||||||
|
"weightPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iIzJhMmEyYSIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTAiIGZpbGw9IiM2NjYiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIj7np7Dpho08L3RleHQ+PC9zdmc+" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 2, |
||||||
|
"buyer": "重庆牧业", |
||||||
|
"tradeTime": "2024-01-15 14: 15", |
||||||
|
"quantity": 8, |
||||||
|
"weight": 1120, |
||||||
|
"weightPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iIzJhMmEyYSIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTAiIGZpbGw9IiM2NjYiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIj7np7Dpho08L3RleHQ+PC9zdmc+" |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 2, |
||||||
|
"name": "达娃次仁", |
||||||
|
"licensePlate": "川A99999", |
||||||
|
"yakCount": 18, |
||||||
|
"contact": "139****1234", |
||||||
|
"origin": "红原县瓦切镇", |
||||||
|
"quarantineNo": "HY2024002", |
||||||
|
"entryTime": "2024-01-15 09: 15", |
||||||
|
"progress": 50, |
||||||
|
"tradedCount": 9, |
||||||
|
"pendingCount": 9, |
||||||
|
"entryPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjMmEyYTJhIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzY2NiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPuWFpeWcuueXp+eJhzwvdGV4dD48L3N2Zz4=", |
||||||
|
"quarantineCert": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjMmEyYTJhIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzY2NiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPuajgOeWq+ivgTwvdGV4dD48L3N2Zz4=", |
||||||
|
"orders": [ |
||||||
|
{ |
||||||
|
"id": 3, |
||||||
|
"buyer": "昆明食品厂", |
||||||
|
"tradeTime": "2024-01-15 11: 30", |
||||||
|
"quantity": 9, |
||||||
|
"weight": 1260, |
||||||
|
"weightPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iIzJhMmEyYSIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTAiIGZpbGw9IiM2NjYiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIj7np7Dpho08L3RleHQ+PC9zdmc+" |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 3, |
||||||
|
"name": "洛桑平措", |
||||||
|
"licensePlate": "川A77777", |
||||||
|
"yakCount": 32, |
||||||
|
"contact": "137****9876", |
||||||
|
"origin": "红原县刷经寺镇", |
||||||
|
"quarantineNo": "HY2024003", |
||||||
|
"entryTime": "2024-01-15 07: 45", |
||||||
|
"progress": 100, |
||||||
|
"tradedCount": 32, |
||||||
|
"pendingCount": 0, |
||||||
|
"entryPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjMmEyYTJhIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzY2NiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPuWFpeWcuueXp+eJhzwvdGV4dD48L3N2Zz4=", |
||||||
|
"quarantineCert": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjMmEyYTJhIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzY2NiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPuajgOeWq+ivgTwvdGV4dD48L3N2Zz4=", |
||||||
|
"orders": [ |
||||||
|
{ |
||||||
|
"id": 4, |
||||||
|
"buyer": "西安牧业集团", |
||||||
|
"tradeTime": "2024-01-15 09: 00", |
||||||
|
"quantity": 20, |
||||||
|
"weight": 2800, |
||||||
|
"weightPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iIzJhMmEyYSIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTAiIGZpbGw9IiM2NjYiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIj7np7Dpho08L3RleHQ+PC9zdmc+" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 5, |
||||||
|
"buyer": "兰州畜牧公司", |
||||||
|
"tradeTime": "2024-01-15 13: 45", |
||||||
|
"quantity": 12, |
||||||
|
"weight": 1680, |
||||||
|
"weightPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iIzJhMmEyYSIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTAiIGZpbGw9IiM2NjYiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIj7np7Dpho08L3RleHQ+PC9zdmc+" |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 4, |
||||||
|
"name": "白玛旺堆", |
||||||
|
"licensePlate": "川A66666", |
||||||
|
"yakCount": 15, |
||||||
|
"contact": "136****5432", |
||||||
|
"origin": "红原县江茸乡", |
||||||
|
"quarantineNo": "HY2024004", |
||||||
|
"entryTime": "2024-01-15 10: 20", |
||||||
|
"progress": 30, |
||||||
|
"tradedCount": 4, |
||||||
|
"pendingCount": 11, |
||||||
|
"entryPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjMmEyYTJhIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzY2NiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPuWFpeWcuueXp+eJhzwvdGV4dD48L3N2Zz4=", |
||||||
|
"quarantineCert": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjMmEyYTJhIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzY2NiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPuajgOeWq+ivgTwvdGV4dD48L3N2Zz4=", |
||||||
|
"orders": [ |
||||||
|
{ |
||||||
|
"id": 6, |
||||||
|
"buyer": "贵阳食品公司", |
||||||
|
"tradeTime": "2024-01-15 12: 10", |
||||||
|
"quantity": 4, |
||||||
|
"weight": 560, |
||||||
|
"weightPhoto": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iIzJhMmEyYSIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTAiIGZpbGw9IiM2NjYiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIj7np7Dpho08L3RleHQ+PC9zdmc+" |
||||||
|
} |
||||||
|
] |
||||||
|
} |
||||||
|
] |
||||||
@ -0,0 +1 @@ |
|||||||
|
|
||||||
@ -0,0 +1,242 @@ |
|||||||
|
{ |
||||||
|
"centerCity": { |
||||||
|
"name": "红原县", |
||||||
|
"coordinates": [102.568685, 32.826358], |
||||||
|
"description": "四川省阿坝州红原县,中国重要的牦牛养殖基地" |
||||||
|
}, |
||||||
|
"geoCoordMap": { |
||||||
|
"红原县": [102.568685, 32.826358], |
||||||
|
"邛溪镇": [102.515, 32.7855], |
||||||
|
"龙日镇": [102.484, 32.3605], |
||||||
|
"麦洼乡": [102.918, 32.9156], |
||||||
|
"阿木乡": [102.789, 32.9154], |
||||||
|
"刷经寺镇": [102.661, 31.9177], |
||||||
|
"瓦切镇": [102.669, 33.2578], |
||||||
|
"查尔玛乡": [101.921, 32.5029], |
||||||
|
"江茸乡": [102.412, 32.3022], |
||||||
|
"安曲镇": [102.222, 32.6709], |
||||||
|
"色地镇": [102.999, 32.865], |
||||||
|
"阿坝县": [101.714684, 32.910406], |
||||||
|
"壤塘县": [101.027085, 32.285498], |
||||||
|
"炉霍县": [100.672937, 31.421821], |
||||||
|
"甘孜县": [99.987638, 31.654244], |
||||||
|
"若尔盖县": [102.96455, 33.547262], |
||||||
|
"松潘县": [103.631452, 32.649103], |
||||||
|
"红原县养殖基地": [102.550539, 32.797395], |
||||||
|
"果洛藏族自治州": [98.871152, 34.255834], |
||||||
|
"成都": [103.958004, 30.772708], |
||||||
|
"玉树藏族自治州": [94.961726, 33.434535], |
||||||
|
"甘孜藏族自治州": [101.226006, 30.470387], |
||||||
|
"拉萨": [91.420246, 29.878931], |
||||||
|
"西宁": [101.74113, 36.641678], |
||||||
|
"兰州": [103.249708, 35.956623], |
||||||
|
"甘南藏族自治州": [103.3049, 34.644479], |
||||||
|
"昌都": [97.399368, 31.248109], |
||||||
|
"重庆": [106.54, 29.59], |
||||||
|
"西安": [108.95, 34.27], |
||||||
|
"昆明": [102.73, 25.04], |
||||||
|
"贵阳": [106.71, 26.57], |
||||||
|
"北京": [116.46, 39.92], |
||||||
|
"上海": [121.48, 31.22], |
||||||
|
"广州": [113.23, 23.16], |
||||||
|
"深圳": [114.07, 22.62], |
||||||
|
"杭州": [120.19, 30.26], |
||||||
|
"南京": [118.78, 32.04], |
||||||
|
"武汉": [114.31, 30.52], |
||||||
|
"长沙": [112.94, 28.19], |
||||||
|
"郑州": [113.65, 34.76], |
||||||
|
"济南": [117.00, 36.65], |
||||||
|
"天津": [117.20, 39.13], |
||||||
|
"石家庄": [114.48, 38.03], |
||||||
|
"太原": [112.53, 37.87], |
||||||
|
"呼和浩特": [111.65, 40.82], |
||||||
|
"银川": [106.27, 38.47], |
||||||
|
"乌鲁木齐": [87.68, 43.77] |
||||||
|
}, |
||||||
|
"tradingModes": { |
||||||
|
"outflow": { |
||||||
|
"title": "销售网络分布图", |
||||||
|
"description": "从红原县向各藏区及重要城市输出牦牛的流向", |
||||||
|
"flows": [ |
||||||
|
{ |
||||||
|
"from": "红原县", |
||||||
|
"to": "果洛藏族自治州", |
||||||
|
"value": 1200, |
||||||
|
"description": "青海果洛州主要销售市场" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"from": "红原县", |
||||||
|
"to": "成都", |
||||||
|
"value": 1100, |
||||||
|
"description": "四川省会城市市场" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"from": "红原县", |
||||||
|
"to": "玉树藏族自治州", |
||||||
|
"value": 980, |
||||||
|
"description": "青海玉树州销售市场" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"from": "红原县", |
||||||
|
"to": "甘孜藏族自治州", |
||||||
|
"value": 850, |
||||||
|
"description": "四川甘孜州销售网络" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"from": "红原县", |
||||||
|
"to": "拉萨", |
||||||
|
"value": 800, |
||||||
|
"description": "西藏自治区首府市场" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"from": "红原县", |
||||||
|
"to": "西宁", |
||||||
|
"value": 720, |
||||||
|
"description": "青海省会城市市场" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"from": "红原县", |
||||||
|
"to": "兰州", |
||||||
|
"value": 680, |
||||||
|
"description": "甘肃省会城市市场" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"from": "红原县", |
||||||
|
"to": "甘南藏族自治州", |
||||||
|
"value": 620, |
||||||
|
"description": "甘肃甘南州销售市场" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"from": "红原县", |
||||||
|
"to": "昌都", |
||||||
|
"value": 550, |
||||||
|
"description": "西藏昌都地区市场" |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
"inflow": { |
||||||
|
"title": "源地供应分布图", |
||||||
|
"description": "各牦牛养殖基地向红原县牦牛交易市场供应的流向", |
||||||
|
"flows": [ |
||||||
|
{ |
||||||
|
"from": "若尔盖县", |
||||||
|
"to": "红原县", |
||||||
|
"value": 1200, |
||||||
|
"description": "若尔盖县草原牦牛供应" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"from": "阿坝县", |
||||||
|
"to": "红原县", |
||||||
|
"value": 1000, |
||||||
|
"description": "阿坝县高原牦牛供应" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"from": "松潘县", |
||||||
|
"to": "红原县", |
||||||
|
"value": 850, |
||||||
|
"description": "松潘县牦牛养殖供应" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"from": "红原县养殖基地", |
||||||
|
"to": "红原县", |
||||||
|
"value": 800, |
||||||
|
"description": "红原县本地养殖基地供应" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"from": "壤塘县", |
||||||
|
"to": "红原县", |
||||||
|
"value": 720, |
||||||
|
"description": "壤塘县牦牛供应" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"from": "炉霍县", |
||||||
|
"to": "红原县", |
||||||
|
"value": 650, |
||||||
|
"description": "炉霍县牦牛养殖供应" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"from": "甘孜县", |
||||||
|
"to": "红原县", |
||||||
|
"value": 580, |
||||||
|
"description": "甘孜县牦牛供应" |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
"local": { |
||||||
|
"title": "红原出栏分布图", |
||||||
|
"description": "红原县各乡镇向县牦牛交易市场的出栏流向", |
||||||
|
"flows": [ |
||||||
|
{ |
||||||
|
"from": "麦洼乡", |
||||||
|
"to": "红原县", |
||||||
|
"value": 1200, |
||||||
|
"description": "麦洼乡牦牛出栏" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"from": "色地镇", |
||||||
|
"to": "红原县", |
||||||
|
"value": 1100, |
||||||
|
"description": "色地镇牦牛出栏" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"from": "阿木乡", |
||||||
|
"to": "红原县", |
||||||
|
"value": 950, |
||||||
|
"description": "阿木乡牦牛出栏" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"from": "邛溪镇", |
||||||
|
"to": "红原县", |
||||||
|
"value": 880, |
||||||
|
"description": "邛溪镇牦牛出栏" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"from": "瓦切镇", |
||||||
|
"to": "红原县", |
||||||
|
"value": 820, |
||||||
|
"description": "瓦切镇牦牛出栏" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"from": "刷经寺镇", |
||||||
|
"to": "红原县", |
||||||
|
"value": 750, |
||||||
|
"description": "刷经寺镇牦牛出栏" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"from": "安曲镇", |
||||||
|
"to": "红原县", |
||||||
|
"value": 680, |
||||||
|
"description": "安曲镇牦牛出栏" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"from": "查尔玛乡", |
||||||
|
"to": "红原县", |
||||||
|
"value": 620, |
||||||
|
"description": "查尔玛乡牦牛出栏" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"from": "龙日镇", |
||||||
|
"to": "红原县", |
||||||
|
"value": 580, |
||||||
|
"description": "龙日镇牦牛出栏" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"from": "江茸乡", |
||||||
|
"to": "红原县", |
||||||
|
"value": 520, |
||||||
|
"description": "江茸乡牦牛出栏" |
||||||
|
} |
||||||
|
] |
||||||
|
} |
||||||
|
}, |
||||||
|
"cityCategories": { |
||||||
|
"production": ["红原县", "拉萨", "西宁", "兰州", "银川", "呼和浩特", "乌鲁木齐"], |
||||||
|
"consumption": ["成都", "重庆", "西安", "北京", "上海", "广州", "深圳", "杭州", "武汉", "昆明", "贵阳"], |
||||||
|
"transit": ["西安", "兰州", "成都", "重庆"] |
||||||
|
}, |
||||||
|
"flowLevels": { |
||||||
|
"high": { "threshold": 600, "color": "#E6A23C", "description": "主要流向" }, |
||||||
|
"medium": { "threshold": 300, "color": "#409EFF", "description": "重要流向" }, |
||||||
|
"low": { "threshold": 0, "color": "#67C23A", "description": "一般流向" } |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,438 @@ |
|||||||
|
<template> |
||||||
|
<div class="dashboard-container"> |
||||||
|
<!-- 标题区域 --> |
||||||
|
<div class="header-section"> |
||||||
|
<div class="header-info left"> |
||||||
|
<!-- <div class="info-item"> |
||||||
|
<span class="status-indicator" :class="isLayoutReady ? 'online' : 'loading'"></span> |
||||||
|
<span>{{ isLayoutReady ? '系统运行正常' : '系统初始化中' }}</span> |
||||||
|
</div> --> |
||||||
|
</div> |
||||||
|
<h1 class="main-title">{{ config?.screen?.title || '智慧活畜交易大数据中心' }}</h1> |
||||||
|
<div class="header-info right"> |
||||||
|
<div class="info-item"> |
||||||
|
<span class="current-time">{{ currentTime || '--:--:--' }}</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- 主要内容区域 --> |
||||||
|
<div v-if="isLayoutReady" class="main-content"> |
||||||
|
<!-- 左侧区域 - 分为左和左中两列 --> |
||||||
|
<div class="left-section"> |
||||||
|
<!-- 左列 --> |
||||||
|
<div class="left-column"> |
||||||
|
<div class="left-top"> |
||||||
|
<RealTimeStats /> |
||||||
|
</div> |
||||||
|
<div class="left-bottom"> |
||||||
|
<YakTradingData /> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- 左中列 --> |
||||||
|
<div class="left-center-column"> |
||||||
|
<div class="left-center-top"> |
||||||
|
<ComprehensiveSalesStats /> |
||||||
|
</div> |
||||||
|
<div class="left-center-bottom"> |
||||||
|
<YakPriceTrend /> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- 中央区域 --> |
||||||
|
<div class="center-section"> |
||||||
|
<div class="map-container"> |
||||||
|
<ChinaMap /> |
||||||
|
<!-- <ScrollingAnnouncement /> --> |
||||||
|
<!-- <div class="floating-table"> |
||||||
|
<TransactionDetails /> |
||||||
|
</div> --> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- 右侧区域 - 分为右中和右两列 --> |
||||||
|
<div class="right-section"> |
||||||
|
<!-- 当供应信息展开时,整个右侧区域显示供应信息 --> |
||||||
|
<div v-if="isSupplyExpanded" class="expanded-supply-container"> |
||||||
|
<SupplyDemandData :force-expanded="true" @expand-change="handleSupplyExpand" /> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- 正常状态下的右侧布局 --> |
||||||
|
<template v-else> |
||||||
|
<!-- 右中列 --> |
||||||
|
<div class="right-center-column"> |
||||||
|
<div class="right-center-top"> |
||||||
|
<ExchangeMonitor /> |
||||||
|
</div> |
||||||
|
<div class="right-center-middle"> |
||||||
|
<YakSalesTypeStats /> |
||||||
|
</div> |
||||||
|
<div class="right-center-bottom"> |
||||||
|
<PurchaserAnalysis /> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- 右列 --> |
||||||
|
<div class="right-column"> |
||||||
|
<div class="right-top"> |
||||||
|
<MarketEnvironmentMonitor /> |
||||||
|
</div> |
||||||
|
<div class="right-middle"> |
||||||
|
<MarketRealtimeMonitor /> |
||||||
|
</div> |
||||||
|
<div class="right-bottom"> |
||||||
|
<SupplyDemandData :force-expanded="false" @expand-change="handleSupplyExpand" /> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- 加载状态 --> |
||||||
|
<div v-else class="loading-container"> |
||||||
|
<div class="loading-content"> |
||||||
|
<div class="loading-spinner"></div> |
||||||
|
<div class="loading-text">正在初始化布局...</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup> |
||||||
|
import { ref, onMounted, onUnmounted, nextTick } from 'vue' |
||||||
|
import { configManager } from './utils/config.js' |
||||||
|
import { dataManager } from './utils/dataManager.js' |
||||||
|
import { dataUpdater } from './utils/dataUpdater.js' |
||||||
|
|
||||||
|
// 导入组件 |
||||||
|
import RealTimeStats from './components/RealTimeStats.vue' |
||||||
|
import YakTradingData from './components/YakTradingData.vue' |
||||||
|
import ComprehensiveSalesStats from './components/ComprehensiveSalesStats.vue' |
||||||
|
import YakPriceTrend from './components/YakPriceTrend.vue' |
||||||
|
import ChinaMap from './components/ChinaMap.vue' |
||||||
|
import ScrollingAnnouncement from './components/ScrollingAnnouncement.vue' |
||||||
|
import TransactionDetails from './components/TransactionDetails.vue' |
||||||
|
import ExchangeMonitor from './components/ExchangeMonitor.vue' |
||||||
|
import YakSalesTypeStats from './components/YakSalesTypeStats.vue' |
||||||
|
import PurchaserAnalysis from './components/PurchaserAnalysis.vue' |
||||||
|
import MarketEnvironmentMonitor from './components/MarketEnvironmentMonitor.vue' |
||||||
|
import MarketRealtimeMonitor from './components/MarketRealtimeMonitor.vue' |
||||||
|
import SupplyDemandData from './components/SupplyDemandData.vue' |
||||||
|
|
||||||
|
// 响应式数据 |
||||||
|
const config = ref(null) |
||||||
|
const currentTime = ref('') |
||||||
|
const isLayoutReady = ref(false) // 控制页面渲染时机 |
||||||
|
const isSupplyExpanded = ref(false) // 控制供应信息组件是否展开 |
||||||
|
|
||||||
|
// 更新时间 |
||||||
|
const updateTime = () => { |
||||||
|
const now = new Date() |
||||||
|
currentTime.value = now.toLocaleString('zh-CN', { |
||||||
|
year: 'numeric', |
||||||
|
month: '2-digit', |
||||||
|
day: '2-digit', |
||||||
|
hour: '2-digit', |
||||||
|
minute: '2-digit', |
||||||
|
second: '2-digit' |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
// 处理供应信息组件展开/收缩 |
||||||
|
const handleSupplyExpand = (expanded) => { |
||||||
|
isSupplyExpanded.value = expanded |
||||||
|
} |
||||||
|
|
||||||
|
// 初始化配置和时间 |
||||||
|
let timeTimer = null |
||||||
|
|
||||||
|
onMounted(async () => { |
||||||
|
// 立即开始更新时间 |
||||||
|
updateTime() |
||||||
|
timeTimer = setInterval(updateTime, 1000) |
||||||
|
|
||||||
|
try { |
||||||
|
// 加载配置 |
||||||
|
console.log('App.vue: 开始加载配置') |
||||||
|
config.value = await configManager.loadConfig() |
||||||
|
console.log('App.vue: 配置加载完成', config.value) |
||||||
|
|
||||||
|
// 设置CSS变量 |
||||||
|
console.log('App.vue: 设置CSS变量') |
||||||
|
configManager.setCSSVariables() |
||||||
|
|
||||||
|
// 等待CSS变量生效和DOM更新 |
||||||
|
await nextTick() |
||||||
|
|
||||||
|
// 再等待一帧确保样式完全应用 |
||||||
|
await new Promise(resolve => requestAnimationFrame(resolve)) |
||||||
|
console.log('App.vue: CSS变量已生效') |
||||||
|
|
||||||
|
// 加载数据配置 |
||||||
|
console.log('App.vue: 开始加载数据配置') |
||||||
|
const loadedData = await dataManager.loadData() |
||||||
|
console.log('App.vue: 数据配置加载完成', loadedData) |
||||||
|
|
||||||
|
// 标记布局准备完成,开始渲染组件 |
||||||
|
console.log('App.vue: 布局准备完成,开始渲染组件') |
||||||
|
isLayoutReady.value = true |
||||||
|
|
||||||
|
console.log('Dashboard 完全初始化完成') |
||||||
|
} catch (error) { |
||||||
|
console.error('Failed to initialize dashboard:', error) |
||||||
|
// 使用默认配置 |
||||||
|
config.value = configManager.getDefaultConfig() |
||||||
|
configManager.setCSSVariables() |
||||||
|
|
||||||
|
// 等待DOM更新后显示页面 |
||||||
|
await nextTick() |
||||||
|
await new Promise(resolve => requestAnimationFrame(resolve)) |
||||||
|
isLayoutReady.value = true |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
onUnmounted(() => { |
||||||
|
if (timeTimer) { |
||||||
|
clearInterval(timeTimer) |
||||||
|
} |
||||||
|
}) |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
/* 标题区域 */ |
||||||
|
.header-section { |
||||||
|
height: 84px; |
||||||
|
display: flex; |
||||||
|
justify-content: space-between; |
||||||
|
align-items: center; |
||||||
|
padding: 0 40px; |
||||||
|
background: url('./images/大标题背景.png') center/contain no-repeat; |
||||||
|
background-size: auto 84px; |
||||||
|
border: 1px solid rgba(64, 158, 255, 0.3); |
||||||
|
border-radius: 12px; |
||||||
|
backdrop-filter: blur(10px); |
||||||
|
position: relative; |
||||||
|
z-index: 10; |
||||||
|
flex-shrink: 0; |
||||||
|
background-size: 100% 84px; |
||||||
|
} |
||||||
|
|
||||||
|
.main-title { |
||||||
|
font-size: 36px; |
||||||
|
font-weight: 700; |
||||||
|
background: linear-gradient(180deg, #fff 50%, #44c1ff); |
||||||
|
-webkit-background-clip: text; |
||||||
|
-webkit-text-fill-color: transparent; |
||||||
|
background-clip: text; |
||||||
|
text-align: center; |
||||||
|
letter-spacing: 3px; |
||||||
|
text-shadow: 0 0 20px rgba(64, 158, 255, 0.5); |
||||||
|
flex: 2; |
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
align-items: center; |
||||||
|
margin-bottom: 15px; |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
.header-info { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 24px; |
||||||
|
flex: 1; |
||||||
|
|
||||||
|
&.left { |
||||||
|
justify-content: flex-start; |
||||||
|
} |
||||||
|
|
||||||
|
&.right { |
||||||
|
justify-content: flex-end; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.info-item { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 8px; |
||||||
|
font-size: 14px; |
||||||
|
color: #a0a8b8; |
||||||
|
} |
||||||
|
|
||||||
|
.current-time { |
||||||
|
font-family: 'Courier New', monospace; |
||||||
|
font-weight: bold; |
||||||
|
color: #409EFF; |
||||||
|
} |
||||||
|
|
||||||
|
/* 主要内容区域 */ |
||||||
|
.main-content { |
||||||
|
flex: 1; |
||||||
|
display: flex; |
||||||
|
gap: 16px; |
||||||
|
min-height: 0; |
||||||
|
padding-bottom: 20px; |
||||||
|
} |
||||||
|
|
||||||
|
/* 左侧区域布局 */ |
||||||
|
.left-section { |
||||||
|
flex: 1; |
||||||
|
display: flex; |
||||||
|
gap: 12px; |
||||||
|
min-width: 0; |
||||||
|
} |
||||||
|
|
||||||
|
.left-column { |
||||||
|
flex: 1; |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
gap: 12px; |
||||||
|
min-width: 0; |
||||||
|
} |
||||||
|
|
||||||
|
.left-center-column { |
||||||
|
flex: 1; |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
gap: 12px; |
||||||
|
min-width: 0; |
||||||
|
} |
||||||
|
|
||||||
|
.left-top, |
||||||
|
.left-bottom, |
||||||
|
.left-center-top, |
||||||
|
.left-center-bottom { |
||||||
|
flex: 1; |
||||||
|
min-height: 0; |
||||||
|
} |
||||||
|
|
||||||
|
/* 中央区域布局 */ |
||||||
|
.center-section { |
||||||
|
flex: 1; |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
min-width: 0; |
||||||
|
} |
||||||
|
|
||||||
|
.map-container { |
||||||
|
position: relative; |
||||||
|
flex: 1; |
||||||
|
min-height: 0; |
||||||
|
} |
||||||
|
|
||||||
|
.floating-table { |
||||||
|
position: absolute; |
||||||
|
bottom: 20px; |
||||||
|
left: 20px; |
||||||
|
right: 20px; |
||||||
|
height: 200px; |
||||||
|
background: rgba(26, 31, 46, 0.95); |
||||||
|
border: 1px solid rgba(64, 158, 255, 0.4); |
||||||
|
border-radius: 8px; |
||||||
|
backdrop-filter: blur(10px); |
||||||
|
z-index: 100; |
||||||
|
} |
||||||
|
|
||||||
|
/* 右侧区域布局 */ |
||||||
|
.right-section { |
||||||
|
flex: 1; |
||||||
|
display: flex; |
||||||
|
gap: 12px; |
||||||
|
min-width: 0; |
||||||
|
} |
||||||
|
|
||||||
|
.right-center-column { |
||||||
|
flex: 1; |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
gap: 12px; |
||||||
|
min-width: 0; |
||||||
|
} |
||||||
|
|
||||||
|
.right-column { |
||||||
|
flex: 1; |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
gap: 12px; |
||||||
|
min-width: 0; |
||||||
|
} |
||||||
|
|
||||||
|
.right-center-top, |
||||||
|
.right-center-middle, |
||||||
|
.right-center-bottom, |
||||||
|
.right-top, |
||||||
|
.right-middle, |
||||||
|
.right-bottom { |
||||||
|
flex: 1; |
||||||
|
min-height: 0; |
||||||
|
} |
||||||
|
|
||||||
|
/* 展开的供应信息容器 */ |
||||||
|
.expanded-supply-container { |
||||||
|
flex: 1; |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
min-height: 0; |
||||||
|
} |
||||||
|
|
||||||
|
/* 状态指示器动画 */ |
||||||
|
@keyframes pulse { |
||||||
|
|
||||||
|
0%, |
||||||
|
100% { |
||||||
|
opacity: 1; |
||||||
|
transform: scale(1); |
||||||
|
} |
||||||
|
|
||||||
|
50% { |
||||||
|
opacity: 0.7; |
||||||
|
transform: scale(1.1); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.status-indicator.online { |
||||||
|
animation: pulse 2s infinite; |
||||||
|
} |
||||||
|
|
||||||
|
.status-indicator.loading { |
||||||
|
background: #E6A23C; |
||||||
|
box-shadow: 0 0 8px rgba(230, 162, 60, 0.6); |
||||||
|
animation: pulse 1s infinite; |
||||||
|
} |
||||||
|
|
||||||
|
/* 加载状态样式 */ |
||||||
|
.loading-container { |
||||||
|
flex: 1; |
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
align-items: center; |
||||||
|
min-height: 400px; |
||||||
|
} |
||||||
|
|
||||||
|
.loading-content { |
||||||
|
text-align: center; |
||||||
|
color: #409EFF; |
||||||
|
} |
||||||
|
|
||||||
|
.loading-spinner { |
||||||
|
width: 40px; |
||||||
|
height: 40px; |
||||||
|
border: 3px solid rgba(64, 158, 255, 0.3); |
||||||
|
border-top: 3px solid #409EFF; |
||||||
|
border-radius: 50%; |
||||||
|
animation: spin 1s linear infinite; |
||||||
|
margin: 0 auto 16px; |
||||||
|
} |
||||||
|
|
||||||
|
.loading-text { |
||||||
|
font-size: 16px; |
||||||
|
font-weight: 500; |
||||||
|
color: #a0a8b8; |
||||||
|
} |
||||||
|
|
||||||
|
@keyframes spin { |
||||||
|
0% { transform: rotate(0deg); } |
||||||
|
100% { transform: rotate(360deg); } |
||||||
|
} |
||||||
|
</style> |
||||||
@ -0,0 +1,70 @@ |
|||||||
|
<template> |
||||||
|
<div class="chart-card"> |
||||||
|
<div class="card-title">{{ title }}</div> |
||||||
|
<div class="card-content"> |
||||||
|
<slot></slot> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup> |
||||||
|
defineProps({ |
||||||
|
title: { |
||||||
|
type: String, |
||||||
|
required: true |
||||||
|
} |
||||||
|
}) |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
.chart-card { |
||||||
|
background: linear-gradient(135deg, #1a1f2e 0%, #16213e 100%); |
||||||
|
border-radius: 8px; |
||||||
|
padding: 16px; |
||||||
|
border: 1px solid rgba(64, 158, 255, 0.3); |
||||||
|
height: 100%; |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
position: relative; |
||||||
|
overflow: hidden; |
||||||
|
} |
||||||
|
|
||||||
|
.chart-card::before { |
||||||
|
content: ''; |
||||||
|
position: absolute; |
||||||
|
top: 0; |
||||||
|
left: 0; |
||||||
|
right: 0; |
||||||
|
height: 2px; |
||||||
|
background: linear-gradient(90deg, #409EFF, #67C23A, #E6A23C); |
||||||
|
opacity: 0.8; |
||||||
|
} |
||||||
|
|
||||||
|
.card-title { |
||||||
|
font-size: 18px; |
||||||
|
font-weight: 700; |
||||||
|
background: linear-gradient(135deg, #409EFF 0%, #67C23A 50%, #E6A23C 100%); |
||||||
|
-webkit-background-clip: text; |
||||||
|
-webkit-text-fill-color: transparent; |
||||||
|
background-clip: text; |
||||||
|
margin-bottom: 12px; |
||||||
|
text-align: left; |
||||||
|
position: relative; |
||||||
|
z-index: 1; |
||||||
|
} |
||||||
|
|
||||||
|
.card-content { |
||||||
|
flex: 1; |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
position: relative; |
||||||
|
z-index: 1; |
||||||
|
} |
||||||
|
|
||||||
|
.chart-card:hover { |
||||||
|
border-color: rgba(64, 158, 255, 0.6); |
||||||
|
box-shadow: 0 4px 16px rgba(64, 158, 255, 0.2); |
||||||
|
transform: translateY(-2px); |
||||||
|
transition: all 0.3s ease; |
||||||
|
} |
||||||
|
</style> |
||||||
@ -0,0 +1,137 @@ |
|||||||
|
<template> |
||||||
|
<BaseCard title="北京走势图"> |
||||||
|
<div ref="chartRef" class="chart"></div> |
||||||
|
</BaseCard> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup> |
||||||
|
import { ref, onMounted, onUnmounted } from 'vue' |
||||||
|
import * as echarts from 'echarts' |
||||||
|
import BaseCard from './BaseCard.vue' |
||||||
|
|
||||||
|
const chartRef = ref(null) |
||||||
|
let chartInstance = null |
||||||
|
|
||||||
|
// 模拟数据 |
||||||
|
const months = ['一月', '二月', '三月', '四月', '五月', '六月'] |
||||||
|
const data = [20, 40, 60, 80, 60, 100] |
||||||
|
|
||||||
|
const initChart = () => { |
||||||
|
if (!chartRef.value) return |
||||||
|
|
||||||
|
chartInstance = echarts.init(chartRef.value) |
||||||
|
|
||||||
|
const option = { |
||||||
|
tooltip: { |
||||||
|
trigger: 'axis', |
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.8)', |
||||||
|
borderColor: '#409EFF', |
||||||
|
textStyle: { |
||||||
|
color: '#fff', |
||||||
|
fontSize: 12 |
||||||
|
}, |
||||||
|
formatter: function(params) { |
||||||
|
return `${params[0].name}<br/>转牛数量: ${params[0].value}头` |
||||||
|
} |
||||||
|
}, |
||||||
|
grid: { |
||||||
|
left: '8%', |
||||||
|
right: '8%', |
||||||
|
bottom: '15%', |
||||||
|
top: '10%' |
||||||
|
}, |
||||||
|
xAxis: { |
||||||
|
type: 'category', |
||||||
|
data: months, |
||||||
|
axisLine: { |
||||||
|
lineStyle: { |
||||||
|
color: '#4a5568' |
||||||
|
} |
||||||
|
}, |
||||||
|
axisTick: { |
||||||
|
show: false |
||||||
|
}, |
||||||
|
axisLabel: { |
||||||
|
color: '#a0a8b8', |
||||||
|
fontSize: 11 |
||||||
|
} |
||||||
|
}, |
||||||
|
yAxis: { |
||||||
|
type: 'value', |
||||||
|
splitLine: { |
||||||
|
lineStyle: { |
||||||
|
color: '#2d3748', |
||||||
|
type: 'dashed' |
||||||
|
} |
||||||
|
}, |
||||||
|
axisLine: { |
||||||
|
show: false |
||||||
|
}, |
||||||
|
axisTick: { |
||||||
|
show: false |
||||||
|
}, |
||||||
|
axisLabel: { |
||||||
|
color: '#a0a8b8', |
||||||
|
fontSize: 11 |
||||||
|
} |
||||||
|
}, |
||||||
|
series: [{ |
||||||
|
name: '转牛数量', |
||||||
|
type: 'bar', |
||||||
|
data: data, |
||||||
|
barWidth: '60%', |
||||||
|
itemStyle: { |
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
||||||
|
{ offset: 0, color: '#409EFF' }, |
||||||
|
{ offset: 1, color: '#1976D2' } |
||||||
|
]), |
||||||
|
borderRadius: [4, 4, 0, 0] |
||||||
|
}, |
||||||
|
emphasis: { |
||||||
|
itemStyle: { |
||||||
|
shadowBlur: 10, |
||||||
|
shadowColor: 'rgba(64, 158, 255, 0.8)' |
||||||
|
} |
||||||
|
} |
||||||
|
}] |
||||||
|
} |
||||||
|
|
||||||
|
chartInstance.setOption(option) |
||||||
|
|
||||||
|
// 添加动态效果 |
||||||
|
let currentIndex = 0 |
||||||
|
const timer = setInterval(() => { |
||||||
|
chartInstance.dispatchAction({ |
||||||
|
type: 'showTip', |
||||||
|
seriesIndex: 0, |
||||||
|
dataIndex: currentIndex |
||||||
|
}) |
||||||
|
currentIndex = (currentIndex + 1) % data.length |
||||||
|
}, 3000) |
||||||
|
|
||||||
|
chartInstance._autoTimer = timer |
||||||
|
} |
||||||
|
|
||||||
|
onMounted(() => { |
||||||
|
initChart() |
||||||
|
window.addEventListener('resize', () => { |
||||||
|
chartInstance?.resize() |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
onUnmounted(() => { |
||||||
|
if (chartInstance) { |
||||||
|
if (chartInstance._autoTimer) { |
||||||
|
clearInterval(chartInstance._autoTimer) |
||||||
|
} |
||||||
|
chartInstance.dispose() |
||||||
|
} |
||||||
|
}) |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
.chart { |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
} |
||||||
|
</style> |
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,271 @@ |
|||||||
|
<template> |
||||||
|
<BaseCard title="综合销售统计"> |
||||||
|
<div class="stats-container"> |
||||||
|
<!-- 切换标签 --> |
||||||
|
<div class="tab-container"> |
||||||
|
<div |
||||||
|
v-for="(tab, index) in tabs" |
||||||
|
:key="tab.key" |
||||||
|
class="tab-item" |
||||||
|
:class="{ active: activeTab === tab.key }" |
||||||
|
@click="switchTab(tab.key)" |
||||||
|
> |
||||||
|
{{ tab.name }} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- 图表区域 --> |
||||||
|
<div class="chart-container"> |
||||||
|
<div ref="chartRef" class="chart"></div> |
||||||
|
</div> |
||||||
|
|
||||||
|
|
||||||
|
</div> |
||||||
|
</BaseCard> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup> |
||||||
|
import { ref, onMounted, onUnmounted, reactive, computed, watch } from 'vue' |
||||||
|
import * as echarts from 'echarts' |
||||||
|
import BaseCard from './BaseCard.vue' |
||||||
|
import { dataManager } from '../utils/dataManager.js' |
||||||
|
|
||||||
|
const chartRef = ref(null) |
||||||
|
let chartInstance = null |
||||||
|
|
||||||
|
// 当前激活的标签 |
||||||
|
const activeTab = ref('localCountySales') |
||||||
|
|
||||||
|
// 标签配置 |
||||||
|
const tabs = [ |
||||||
|
{ key: 'localCountySales', name: '本县销售统计' }, |
||||||
|
{ key: 'overallSalesDistribution', name: '总体销售分布' }, |
||||||
|
{ key: 'purchaseRegionDistribution', name: '采购地区分布' } |
||||||
|
] |
||||||
|
|
||||||
|
// 响应式数据 |
||||||
|
const statsData = reactive({ |
||||||
|
localCountySales: [], |
||||||
|
overallSalesDistribution: [], |
||||||
|
purchaseRegionDistribution: [] |
||||||
|
}) |
||||||
|
|
||||||
|
// 颜色配置 |
||||||
|
const colors = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399', '#8B5CF6'] |
||||||
|
|
||||||
|
// 当前图表数据 |
||||||
|
const currentChartData = computed(() => { |
||||||
|
return statsData[activeTab.value] || [] |
||||||
|
}) |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 切换标签 |
||||||
|
const switchTab = (tabKey) => { |
||||||
|
activeTab.value = tabKey |
||||||
|
} |
||||||
|
|
||||||
|
// 加载数据 |
||||||
|
const loadData = async () => { |
||||||
|
try { |
||||||
|
await dataManager.loadData() |
||||||
|
const data = dataManager.getData('comprehensiveSalesStats') |
||||||
|
if (data) { |
||||||
|
Object.assign(statsData, data) |
||||||
|
console.log('综合销售统计数据加载成功:', data) |
||||||
|
} else { |
||||||
|
console.warn('未找到综合销售统计数据') |
||||||
|
// 使用默认数据 |
||||||
|
loadDefaultData() |
||||||
|
} |
||||||
|
} catch (error) { |
||||||
|
console.error('加载综合销售统计数据失败:', error) |
||||||
|
loadDefaultData() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 加载默认数据 |
||||||
|
const loadDefaultData = () => { |
||||||
|
statsData.localCountySales = [ |
||||||
|
{ name: 'A县', value: 1240 }, |
||||||
|
{ name: 'B县', value: 890 }, |
||||||
|
{ name: 'C县', value: 560 }, |
||||||
|
{ name: 'D县', value: 450 } |
||||||
|
] |
||||||
|
statsData.overallSalesDistribution = [ |
||||||
|
{ name: 'A市', value: 2340 }, |
||||||
|
{ name: 'B市', value: 1890 }, |
||||||
|
{ name: 'C市', value: 1456 }, |
||||||
|
{ name: 'D市', value: 1234 } |
||||||
|
] |
||||||
|
statsData.purchaseRegionDistribution = [ |
||||||
|
{ name: 'A省', value: 1560 }, |
||||||
|
{ name: 'B省', value: 1230 }, |
||||||
|
{ name: 'C省', value: 980 }, |
||||||
|
{ name: 'D省', value: 750 } |
||||||
|
] |
||||||
|
} |
||||||
|
|
||||||
|
// 初始化图表 |
||||||
|
const initChart = () => { |
||||||
|
if (!chartRef.value) return |
||||||
|
|
||||||
|
chartInstance = echarts.init(chartRef.value) |
||||||
|
updateChart() |
||||||
|
} |
||||||
|
|
||||||
|
// 更新图表 |
||||||
|
const updateChart = () => { |
||||||
|
if (!chartInstance) return |
||||||
|
|
||||||
|
const data = currentChartData.value.map((item, index) => ({ |
||||||
|
name: item.name, |
||||||
|
value: item.value, |
||||||
|
itemStyle: { |
||||||
|
color: colors[index % colors.length], |
||||||
|
borderRadius: 4, |
||||||
|
borderColor: '#1a1f2e', |
||||||
|
borderWidth: 2 |
||||||
|
} |
||||||
|
})) |
||||||
|
|
||||||
|
const option = { |
||||||
|
tooltip: { |
||||||
|
trigger: 'item', |
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.8)', |
||||||
|
borderColor: '#409EFF', |
||||||
|
textStyle: { |
||||||
|
color: '#fff', |
||||||
|
fontSize: 12 |
||||||
|
}, |
||||||
|
formatter: '{b}: {c}头 ({d}%)' |
||||||
|
}, |
||||||
|
legend: { |
||||||
|
orient: 'horizontal', |
||||||
|
bottom: '5%', |
||||||
|
left: 'center', |
||||||
|
itemGap: 15, |
||||||
|
textStyle: { |
||||||
|
color: '#a0a8b8', |
||||||
|
fontSize: 12 |
||||||
|
}, |
||||||
|
itemWidth: 12, |
||||||
|
itemHeight: 12, |
||||||
|
formatter: function(name) { |
||||||
|
const item = currentChartData.value.find(d => d.name === name); |
||||||
|
return `${name}: ${item ? item.value : 0}头`; |
||||||
|
} |
||||||
|
}, |
||||||
|
series: [{ |
||||||
|
name: tabs.find(t => t.key === activeTab.value)?.name || '销售统计', |
||||||
|
type: 'pie', |
||||||
|
radius: ['35%', '60%'], |
||||||
|
center: ['50%', '40%'], |
||||||
|
data: data, |
||||||
|
emphasis: { |
||||||
|
itemStyle: { |
||||||
|
shadowBlur: 10, |
||||||
|
shadowOffsetX: 0, |
||||||
|
shadowColor: 'rgba(0, 0, 0, 0.5)' |
||||||
|
} |
||||||
|
}, |
||||||
|
labelLine: { |
||||||
|
show: false |
||||||
|
}, |
||||||
|
label: { |
||||||
|
show: true, |
||||||
|
position: 'inside', |
||||||
|
formatter: '{d}%', |
||||||
|
fontSize: 13, |
||||||
|
color: '#fff', |
||||||
|
fontWeight: 'bold' |
||||||
|
}, |
||||||
|
animationType: 'scale', |
||||||
|
animationEasing: 'elasticOut', |
||||||
|
animationDelay: function (idx) { |
||||||
|
return Math.random() * 200; |
||||||
|
} |
||||||
|
}] |
||||||
|
} |
||||||
|
|
||||||
|
chartInstance.setOption(option, true) |
||||||
|
} |
||||||
|
|
||||||
|
// 监听activeTab变化 |
||||||
|
watch(activeTab, () => { |
||||||
|
updateChart() |
||||||
|
}) |
||||||
|
|
||||||
|
onMounted(async () => { |
||||||
|
await loadData() |
||||||
|
initChart() |
||||||
|
|
||||||
|
window.addEventListener('resize', () => { |
||||||
|
chartInstance?.resize() |
||||||
|
}) |
||||||
|
|
||||||
|
// 监听数据重新加载事件 |
||||||
|
window.addEventListener('dataReloaded', async () => { |
||||||
|
console.log('收到数据重新加载事件,重新加载综合销售统计数据') |
||||||
|
await loadData() |
||||||
|
updateChart() |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
onUnmounted(() => { |
||||||
|
if (chartInstance) { |
||||||
|
chartInstance.dispose() |
||||||
|
} |
||||||
|
}) |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
.stats-container { |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
height: 100%; |
||||||
|
} |
||||||
|
|
||||||
|
.tab-container { |
||||||
|
display: flex; |
||||||
|
margin-bottom: 12px; |
||||||
|
background: rgba(26, 31, 46, 0.5); |
||||||
|
border-radius: 6px; |
||||||
|
padding: 2px; |
||||||
|
} |
||||||
|
|
||||||
|
.tab-item { |
||||||
|
flex: 1; |
||||||
|
text-align: center; |
||||||
|
padding: 6px 8px; |
||||||
|
font-size: 13px; |
||||||
|
color: #a0a8b8; |
||||||
|
cursor: pointer; |
||||||
|
border-radius: 4px; |
||||||
|
transition: all 0.3s ease; |
||||||
|
user-select: none; |
||||||
|
} |
||||||
|
|
||||||
|
.tab-item:hover { |
||||||
|
color: #409EFF; |
||||||
|
background: rgba(64, 158, 255, 0.1); |
||||||
|
} |
||||||
|
|
||||||
|
.tab-item.active { |
||||||
|
color: #ffffff; |
||||||
|
background: #409EFF; |
||||||
|
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.3); |
||||||
|
} |
||||||
|
|
||||||
|
.chart-container { |
||||||
|
flex: 1; |
||||||
|
min-height: 140px; |
||||||
|
} |
||||||
|
|
||||||
|
.chart { |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
</style> |
||||||
@ -0,0 +1,223 @@ |
|||||||
|
<template> |
||||||
|
<BaseCard title="采购商户来源分析"> |
||||||
|
<div ref="chartRef" class="chart"></div> |
||||||
|
</BaseCard> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup> |
||||||
|
import { ref, onMounted, onUnmounted } from 'vue' |
||||||
|
import * as echarts from 'echarts' |
||||||
|
import BaseCard from './BaseCard.vue' |
||||||
|
|
||||||
|
const chartRef = ref(null) |
||||||
|
let chartInstance = null |
||||||
|
|
||||||
|
// 采购商户来源数据 - 基于销售网络分布图的目标区域 |
||||||
|
const regionsData = ref([ |
||||||
|
{ name: '果洛藏族自治州', value: 156, color: '#409EFF', description: '青海果洛州采购商户' }, |
||||||
|
{ name: '成都', value: 142, color: '#67C23A', description: '四川省会城市采购商户' }, |
||||||
|
{ name: '玉树藏族自治州', value: 128, color: '#E6A23C', description: '青海玉树州采购商户' }, |
||||||
|
{ name: '甘孜藏族自治州', value: 115, color: '#F56C6C', description: '四川甘孜州采购商户' }, |
||||||
|
{ name: '拉萨', value: 98, color: '#909399', description: '西藏自治区采购商户' }, |
||||||
|
{ name: '西宁', value: 89, color: '#00D4AA', description: '青海省会城市采购商户' }, |
||||||
|
{ name: '兰州', value: 76, color: '#8B5CF6', description: '甘肃省会城市采购商户' }, |
||||||
|
{ name: '甘南藏族自治州', value: 68, color: '#FF6B9D', description: '甘肃甘南州采购商户' }, |
||||||
|
{ name: '昌都', value: 54, color: '#FFB800', description: '西藏昌都地区采购商户' } |
||||||
|
]) |
||||||
|
|
||||||
|
// 数据更新函数 |
||||||
|
const updateData = () => { |
||||||
|
regionsData.value.forEach(region => { |
||||||
|
// 小幅度随机调整数据 |
||||||
|
const variance = (Math.random() - 0.5) * 20 // ±10的变化 |
||||||
|
region.value = Math.max(20, Math.floor(region.value + variance)) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const initChart = () => { |
||||||
|
if (!chartRef.value) return |
||||||
|
|
||||||
|
chartInstance = echarts.init(chartRef.value) |
||||||
|
|
||||||
|
const updateChart = () => { |
||||||
|
const option = { |
||||||
|
tooltip: { |
||||||
|
trigger: 'axis', |
||||||
|
backgroundColor: 'rgba(15, 25, 45, 0.95)', |
||||||
|
borderColor: '#00d4ff', |
||||||
|
borderWidth: 1, |
||||||
|
textStyle: { |
||||||
|
color: '#fff', |
||||||
|
fontSize: 15 |
||||||
|
}, |
||||||
|
formatter: function(params) { |
||||||
|
const item = regionsData.value[params[0].dataIndex] |
||||||
|
return `<div style="padding: 8px;"> |
||||||
|
<strong style="color: #00d4ff;">${params[0].name}</strong><br/> |
||||||
|
<span style="color: #67c23a;">采购商户: ${params[0].value} 家</span><br/> |
||||||
|
<span style="color: #8cc8ff;">${item.description}</span> |
||||||
|
</div>` |
||||||
|
} |
||||||
|
}, |
||||||
|
grid: { |
||||||
|
left: '8%', |
||||||
|
right: '8%', |
||||||
|
bottom: '20%', |
||||||
|
top: '15%', |
||||||
|
containLabel: true |
||||||
|
}, |
||||||
|
xAxis: { |
||||||
|
type: 'category', |
||||||
|
data: regionsData.value.map(item => item.name), |
||||||
|
axisLine: { |
||||||
|
lineStyle: { |
||||||
|
color: '#4a5568' |
||||||
|
} |
||||||
|
}, |
||||||
|
axisTick: { |
||||||
|
show: false |
||||||
|
}, |
||||||
|
axisLabel: { |
||||||
|
color: '#a0a8b8', |
||||||
|
fontSize: 12, |
||||||
|
rotate: 30, |
||||||
|
interval: 0, |
||||||
|
formatter: function(value) { |
||||||
|
// 对长地名进行换行处理 |
||||||
|
if (value.length > 6) { |
||||||
|
return value.substring(0, 4) + '\n' + value.substring(4) |
||||||
|
} |
||||||
|
return value |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
yAxis: { |
||||||
|
type: 'value', |
||||||
|
name: '采购商户数量(家)', |
||||||
|
nameTextStyle: { |
||||||
|
color: '#a0a8b8', |
||||||
|
fontSize: 13 |
||||||
|
}, |
||||||
|
splitLine: { |
||||||
|
lineStyle: { |
||||||
|
color: '#2d3748', |
||||||
|
type: 'dashed' |
||||||
|
} |
||||||
|
}, |
||||||
|
axisLine: { |
||||||
|
show: false |
||||||
|
}, |
||||||
|
axisTick: { |
||||||
|
show: false |
||||||
|
}, |
||||||
|
axisLabel: { |
||||||
|
color: '#a0a8b8', |
||||||
|
fontSize: 13 |
||||||
|
} |
||||||
|
}, |
||||||
|
series: [{ |
||||||
|
name: '采购商户数量', |
||||||
|
type: 'bar', |
||||||
|
data: regionsData.value.map((item, index) => ({ |
||||||
|
value: item.value, |
||||||
|
itemStyle: { |
||||||
|
color: { |
||||||
|
type: 'linear', |
||||||
|
x: 0, |
||||||
|
y: 0, |
||||||
|
x2: 0, |
||||||
|
y2: 1, |
||||||
|
colorStops: [{ |
||||||
|
offset: 0, |
||||||
|
color: item.color |
||||||
|
}, { |
||||||
|
offset: 1, |
||||||
|
color: item.color + '40' // 添加透明度 |
||||||
|
}] |
||||||
|
}, |
||||||
|
borderRadius: [6, 6, 0, 0], |
||||||
|
borderWidth: 2, |
||||||
|
borderColor: item.color, |
||||||
|
shadowBlur: 8, |
||||||
|
shadowColor: item.color + '30' |
||||||
|
} |
||||||
|
})), |
||||||
|
barWidth: '60%', |
||||||
|
emphasis: { |
||||||
|
itemStyle: { |
||||||
|
shadowBlur: 15, |
||||||
|
shadowColor: 'rgba(0, 212, 255, 0.6)', |
||||||
|
borderColor: '#00d4ff', |
||||||
|
borderWidth: 3 |
||||||
|
}, |
||||||
|
scaleSize: 5 |
||||||
|
}, |
||||||
|
label: { |
||||||
|
show: true, |
||||||
|
position: 'top', |
||||||
|
color: '#fff', |
||||||
|
fontSize: 13, |
||||||
|
fontWeight: 'bold', |
||||||
|
formatter: '{c}家' |
||||||
|
}, |
||||||
|
animationDelay: function (idx) { |
||||||
|
return idx * 100 |
||||||
|
} |
||||||
|
}], |
||||||
|
animationEasing: 'elasticOut', |
||||||
|
animationDelayUpdate: function (idx) { |
||||||
|
return idx * 50 |
||||||
|
} |
||||||
|
} |
||||||
|
chartInstance.setOption(option) |
||||||
|
} |
||||||
|
|
||||||
|
updateChart() |
||||||
|
|
||||||
|
// 添加动态效果 |
||||||
|
let currentIndex = 0 |
||||||
|
const timer = setInterval(() => { |
||||||
|
chartInstance.dispatchAction({ |
||||||
|
type: 'showTip', |
||||||
|
seriesIndex: 0, |
||||||
|
dataIndex: currentIndex |
||||||
|
}) |
||||||
|
currentIndex = (currentIndex + 1) % regionsData.value.length |
||||||
|
}, 3000) |
||||||
|
|
||||||
|
// 定期更新数据 |
||||||
|
const dataTimer = setInterval(() => { |
||||||
|
updateData() |
||||||
|
updateChart() |
||||||
|
}, 12000) // 12秒更新一次 |
||||||
|
|
||||||
|
chartInstance._autoTimer = timer |
||||||
|
chartInstance._dataTimer = dataTimer |
||||||
|
} |
||||||
|
|
||||||
|
onMounted(() => { |
||||||
|
initChart() |
||||||
|
window.addEventListener('resize', () => { |
||||||
|
chartInstance?.resize() |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
onUnmounted(() => { |
||||||
|
if (chartInstance) { |
||||||
|
if (chartInstance._autoTimer) { |
||||||
|
clearInterval(chartInstance._autoTimer) |
||||||
|
} |
||||||
|
if (chartInstance._dataTimer) { |
||||||
|
clearInterval(chartInstance._dataTimer) |
||||||
|
} |
||||||
|
chartInstance.dispose() |
||||||
|
} |
||||||
|
}) |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
.chart { |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
} |
||||||
|
</style> |
||||||
@ -0,0 +1,312 @@ |
|||||||
|
<template> |
||||||
|
<BaseCard title="实时交易统计"> |
||||||
|
<!-- 时间维度切换按钮 --> |
||||||
|
<div class="time-dimension-controls"> |
||||||
|
<button |
||||||
|
v-for="dimension in timeDimensions" |
||||||
|
:key="dimension.key" |
||||||
|
@click="currentDimension = dimension.key" |
||||||
|
:class="{ active: currentDimension === dimension.key }" |
||||||
|
class="dimension-btn" |
||||||
|
> |
||||||
|
{{ dimension.label }} |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="stats-container"> |
||||||
|
<div class="stat-item"> |
||||||
|
<div class="stat-value"> |
||||||
|
<div>{{ formatNumber(statsData.yakTotalVolume) }}</div> |
||||||
|
<div class="stat-unit">(头)</div> |
||||||
|
</div> |
||||||
|
<div class="stat-label">牦牛交易总量</div> |
||||||
|
</div> |
||||||
|
<div class="stat-item"> |
||||||
|
<div class="stat-value"> |
||||||
|
<div>{{ formatNumber(statsData.orderTotalVolume) }}</div> |
||||||
|
<div class="stat-unit">(笔)</div> |
||||||
|
</div> |
||||||
|
<div class="stat-label">订单交易总量</div> |
||||||
|
</div> |
||||||
|
<div class="stat-item"> |
||||||
|
<div class="stat-value"> |
||||||
|
<div>{{ formatNumber(statsData.sellerCount) }}</div> |
||||||
|
<div class="stat-unit">(户)</div> |
||||||
|
</div> |
||||||
|
<div class="stat-label">销售商户数量</div> |
||||||
|
</div> |
||||||
|
<div class="stat-item"> |
||||||
|
<div class="stat-value"> |
||||||
|
<div>{{ formatNumber(statsData.buyerCount) }}</div> |
||||||
|
<div class="stat-unit">(户)</div> |
||||||
|
</div> |
||||||
|
<div class="stat-label">采购商户数量</div> |
||||||
|
</div> |
||||||
|
<!-- 调试信息 --> |
||||||
|
<div class="debug-info" v-if="showDebug"> |
||||||
|
<div style="font-size: 10px; color: #666; margin-top: 10px;"> |
||||||
|
调试: {{ statsData.systemStatus }} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</BaseCard> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup> |
||||||
|
import { ref, onMounted, onUnmounted, reactive, watch, computed } from 'vue' |
||||||
|
import BaseCard from './BaseCard.vue' |
||||||
|
import { dataManager } from '../utils/dataManager.js' |
||||||
|
|
||||||
|
// 时间维度配置 |
||||||
|
const timeDimensions = [ |
||||||
|
{ key: 'day', label: '日' }, |
||||||
|
{ key: 'week', label: '周' }, |
||||||
|
{ key: 'year', label: '年' } |
||||||
|
] |
||||||
|
|
||||||
|
// 当前选中的时间维度 |
||||||
|
const currentDimension = ref('day') |
||||||
|
|
||||||
|
// 响应式数据 |
||||||
|
const statsData = reactive({ |
||||||
|
yakTotalVolume: 0, |
||||||
|
orderTotalVolume: 0, |
||||||
|
sellerCount: 0, |
||||||
|
buyerCount: 0, |
||||||
|
systemStatus: '加载中...' |
||||||
|
}) |
||||||
|
|
||||||
|
// 所有维度的数据存储 |
||||||
|
const allDimensionData = reactive({ |
||||||
|
day: { |
||||||
|
yakTotalVolume: 0, |
||||||
|
orderTotalVolume: 0, |
||||||
|
sellerCount: 0, |
||||||
|
buyerCount: 0 |
||||||
|
}, |
||||||
|
week: { |
||||||
|
yakTotalVolume: 0, |
||||||
|
orderTotalVolume: 0, |
||||||
|
sellerCount: 0, |
||||||
|
buyerCount: 0 |
||||||
|
}, |
||||||
|
year: { |
||||||
|
yakTotalVolume: 0, |
||||||
|
orderTotalVolume: 0, |
||||||
|
sellerCount: 0, |
||||||
|
buyerCount: 0 |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
// 调试模式 |
||||||
|
const showDebug = ref(false) |
||||||
|
|
||||||
|
// 格式化数字 |
||||||
|
const formatNumber = (num) => { |
||||||
|
return dataManager.formatNumber(num) |
||||||
|
} |
||||||
|
|
||||||
|
// 生成模拟数据(根据时间维度) |
||||||
|
const generateMockData = (dimension) => { |
||||||
|
const baseData = { |
||||||
|
yakTotalVolume: 8350, |
||||||
|
orderTotalVolume: 1245, |
||||||
|
sellerCount: 156, |
||||||
|
buyerCount: 89 |
||||||
|
} |
||||||
|
|
||||||
|
switch (dimension) { |
||||||
|
case 'day': |
||||||
|
return { |
||||||
|
yakTotalVolume: baseData.yakTotalVolume, |
||||||
|
orderTotalVolume: baseData.orderTotalVolume, |
||||||
|
sellerCount: baseData.sellerCount, |
||||||
|
buyerCount: baseData.buyerCount |
||||||
|
} |
||||||
|
case 'week': |
||||||
|
return { |
||||||
|
yakTotalVolume: Math.floor(baseData.yakTotalVolume * 6.8), |
||||||
|
orderTotalVolume: Math.floor(baseData.orderTotalVolume * 6.5), |
||||||
|
sellerCount: Math.floor(baseData.sellerCount * 1.2), |
||||||
|
buyerCount: Math.floor(baseData.buyerCount * 1.15) |
||||||
|
} |
||||||
|
case 'year': |
||||||
|
return { |
||||||
|
yakTotalVolume: Math.floor(baseData.yakTotalVolume * 285), |
||||||
|
orderTotalVolume: Math.floor(baseData.orderTotalVolume * 295), |
||||||
|
sellerCount: Math.floor(baseData.sellerCount * 3.2), |
||||||
|
buyerCount: Math.floor(baseData.buyerCount * 2.8) |
||||||
|
} |
||||||
|
default: |
||||||
|
return baseData |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 加载数据 |
||||||
|
const loadData = async () => { |
||||||
|
try { |
||||||
|
// 确保数据管理器已经加载完成 |
||||||
|
await dataManager.loadData() |
||||||
|
|
||||||
|
// 为每个时间维度生成数据 |
||||||
|
timeDimensions.forEach(dimension => { |
||||||
|
const data = generateMockData(dimension.key) |
||||||
|
allDimensionData[dimension.key] = data |
||||||
|
}) |
||||||
|
|
||||||
|
// 更新当前显示的数据 |
||||||
|
updateCurrentData() |
||||||
|
|
||||||
|
statsData.systemStatus = '数据加载完成' |
||||||
|
console.log('所有维度数据加载完成:', allDimensionData) |
||||||
|
|
||||||
|
} catch (error) { |
||||||
|
console.error('加载实时统计数据失败:', error) |
||||||
|
statsData.systemStatus = '加载失败' |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 更新当前显示的数据 |
||||||
|
const updateCurrentData = () => { |
||||||
|
const currentData = allDimensionData[currentDimension.value] |
||||||
|
if (currentData) { |
||||||
|
statsData.yakTotalVolume = currentData.yakTotalVolume |
||||||
|
statsData.orderTotalVolume = currentData.orderTotalVolume |
||||||
|
statsData.sellerCount = currentData.sellerCount |
||||||
|
statsData.buyerCount = currentData.buyerCount |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 监听时间维度变化 |
||||||
|
watch(currentDimension, () => { |
||||||
|
updateCurrentData() |
||||||
|
}, { immediate: false }) |
||||||
|
|
||||||
|
onMounted(async () => { |
||||||
|
await loadData() |
||||||
|
|
||||||
|
// 监听数据重新加载事件 |
||||||
|
window.addEventListener('dataReloaded', async () => { |
||||||
|
console.log('收到数据重新加载事件,重新加载实时统计数据') |
||||||
|
await loadData() |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
onUnmounted(() => { |
||||||
|
// 清理事件监听器 |
||||||
|
}) |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
.time-dimension-controls { |
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
gap: 8px; |
||||||
|
margin-bottom: 12px; |
||||||
|
padding: 8px; |
||||||
|
} |
||||||
|
|
||||||
|
.dimension-btn { |
||||||
|
padding: 6px 16px; |
||||||
|
border: 1px solid #2c5282; |
||||||
|
background: rgba(15, 27, 46, 0.8); |
||||||
|
color: #8cc8ff; |
||||||
|
border-radius: 4px; |
||||||
|
cursor: pointer; |
||||||
|
font-size: 13px; |
||||||
|
font-weight: 500; |
||||||
|
transition: all 0.3s ease; |
||||||
|
backdrop-filter: blur(10px); |
||||||
|
min-width: 50px; |
||||||
|
} |
||||||
|
|
||||||
|
.dimension-btn:hover { |
||||||
|
border-color: #00d4ff; |
||||||
|
color: #00d4ff; |
||||||
|
background: rgba(0, 212, 255, 0.1); |
||||||
|
transform: translateY(-1px); |
||||||
|
} |
||||||
|
|
||||||
|
.dimension-btn.active { |
||||||
|
border-color: #00d4ff; |
||||||
|
background: linear-gradient(135deg, #00d4ff, #0099cc); |
||||||
|
color: #fff; |
||||||
|
box-shadow: 0 4px 12px rgba(0, 212, 255, 0.3); |
||||||
|
} |
||||||
|
|
||||||
|
.stats-container { |
||||||
|
display: grid; |
||||||
|
grid-template-columns: 1fr 1fr; |
||||||
|
grid-template-rows: 1fr 1fr; |
||||||
|
gap: 12px; |
||||||
|
height: calc(100% - 60px); |
||||||
|
padding: 8px; |
||||||
|
} |
||||||
|
|
||||||
|
.stat-item { |
||||||
|
position: relative; |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
justify-content: space-between; |
||||||
|
align-items: center; |
||||||
|
text-align: center; |
||||||
|
padding: 20px 12px 16px 12px; |
||||||
|
background: url('../images/数据底座.png') center/contain no-repeat; |
||||||
|
background-size: 80% 70%; |
||||||
|
border-radius: 8px; |
||||||
|
transition: all 0.3s ease; |
||||||
|
min-height: 90px; |
||||||
|
overflow: hidden; |
||||||
|
} |
||||||
|
|
||||||
|
.stat-item:hover { |
||||||
|
transform: translateY(-2px); |
||||||
|
filter: brightness(1.1); |
||||||
|
} |
||||||
|
|
||||||
|
.stat-unit { |
||||||
|
font-size: 16px; |
||||||
|
margin-top: 12px; |
||||||
|
} |
||||||
|
|
||||||
|
.stat-value { |
||||||
|
flex: 1; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
flex-direction: column; |
||||||
|
font-size: 22px; |
||||||
|
font-weight: 900; |
||||||
|
color: #ffffff; |
||||||
|
text-shadow: |
||||||
|
0 0 15px rgba(64, 158, 255, 1), |
||||||
|
0 0 25px rgba(64, 158, 255, 0.8), |
||||||
|
2px 2px 6px rgba(0, 0, 0, 0.9), |
||||||
|
-1px -1px 2px rgba(0, 0, 0, 0.8); |
||||||
|
line-height: 1; |
||||||
|
margin: 4px 0; |
||||||
|
letter-spacing: 1px; |
||||||
|
} |
||||||
|
|
||||||
|
.stat-label { |
||||||
|
font-size: 18px; |
||||||
|
font-weight: 700; |
||||||
|
color: #409EFF; |
||||||
|
/* text-shadow: |
||||||
|
0 0 10px rgba(255, 255, 255, 0.8), |
||||||
|
0 0 15px rgba(64, 158, 255, 0.6), |
||||||
|
2px 2px 4px rgba(0, 0, 0, 0.9), |
||||||
|
-1px -1px 2px rgba(0, 0, 0, 0.7); */ |
||||||
|
line-height: 1.2; |
||||||
|
margin-top: auto; |
||||||
|
letter-spacing: 0.5px; |
||||||
|
} |
||||||
|
|
||||||
|
.debug-info { |
||||||
|
grid-column: 1 / -1; |
||||||
|
text-align: center; |
||||||
|
margin-top: 8px; |
||||||
|
} |
||||||
|
</style> |
||||||
@ -0,0 +1,207 @@ |
|||||||
|
<template> |
||||||
|
<BaseCard title="销售渠道统计"> |
||||||
|
<div class="stats-container"> |
||||||
|
<div class="stats-row"> |
||||||
|
<div class="stat-item"> |
||||||
|
<div class="stat-value">1,234</div> |
||||||
|
<div class="stat-label">线上销售</div> |
||||||
|
</div> |
||||||
|
<div class="stat-item"> |
||||||
|
<div class="stat-value">856</div> |
||||||
|
<div class="stat-label">线下销售</div> |
||||||
|
</div> |
||||||
|
<div class="stat-item"> |
||||||
|
<div class="stat-value">2,090</div> |
||||||
|
<div class="stat-label">总销售额</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="chart-container"> |
||||||
|
<div ref="chartRef" class="chart"></div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</BaseCard> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup> |
||||||
|
import { ref, onMounted, onUnmounted } from 'vue' |
||||||
|
import * as echarts from 'echarts' |
||||||
|
import BaseCard from './BaseCard.vue' |
||||||
|
|
||||||
|
const chartRef = ref(null) |
||||||
|
let chartInstance = null |
||||||
|
|
||||||
|
// 图例数据 |
||||||
|
const legendData = [ |
||||||
|
{ name: '直销', color: '#409EFF', percent: 50 }, |
||||||
|
{ name: '代销', color: '#67C23A', percent: 30 }, |
||||||
|
{ name: '其他', color: '#E6A23C', percent: 20 } |
||||||
|
] |
||||||
|
|
||||||
|
// 模拟数据 |
||||||
|
const data = [ |
||||||
|
{ name: '直销', value: 50 }, |
||||||
|
{ name: '代销', value: 30 }, |
||||||
|
{ name: '其他', value: 20 } |
||||||
|
] |
||||||
|
|
||||||
|
const initChart = () => { |
||||||
|
if (!chartRef.value) return |
||||||
|
|
||||||
|
chartInstance = echarts.init(chartRef.value) |
||||||
|
|
||||||
|
const option = { |
||||||
|
tooltip: { |
||||||
|
trigger: 'item', |
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.8)', |
||||||
|
borderColor: '#409EFF', |
||||||
|
textStyle: { |
||||||
|
color: '#fff', |
||||||
|
fontSize: 12 |
||||||
|
}, |
||||||
|
formatter: '{a} <br/>{b}: {c} ({d}%)' |
||||||
|
}, |
||||||
|
series: [{ |
||||||
|
name: '销售渠道', |
||||||
|
type: 'pie', |
||||||
|
radius: ['40%', '65%'], |
||||||
|
center: ['50%', '50%'], |
||||||
|
data: data.map((item, index) => ({ |
||||||
|
...item, |
||||||
|
itemStyle: { |
||||||
|
color: legendData[index].color, |
||||||
|
borderRadius: 8, |
||||||
|
borderColor: '#1a1f2e', |
||||||
|
borderWidth: 2 |
||||||
|
} |
||||||
|
})), |
||||||
|
emphasis: { |
||||||
|
itemStyle: { |
||||||
|
shadowBlur: 10, |
||||||
|
shadowOffsetX: 0, |
||||||
|
shadowColor: 'rgba(0, 0, 0, 0.5)' |
||||||
|
} |
||||||
|
}, |
||||||
|
labelLine: { |
||||||
|
show: false |
||||||
|
}, |
||||||
|
label: { |
||||||
|
show: false |
||||||
|
} |
||||||
|
}] |
||||||
|
} |
||||||
|
|
||||||
|
chartInstance.setOption(option) |
||||||
|
|
||||||
|
// 自动高亮效果 |
||||||
|
let currentIndex = 0 |
||||||
|
const timer = setInterval(() => { |
||||||
|
chartInstance.dispatchAction({ |
||||||
|
type: 'downplay', |
||||||
|
seriesIndex: 0, |
||||||
|
dataIndex: currentIndex |
||||||
|
}) |
||||||
|
currentIndex = (currentIndex + 1) % data.length |
||||||
|
chartInstance.dispatchAction({ |
||||||
|
type: 'highlight', |
||||||
|
seriesIndex: 0, |
||||||
|
dataIndex: currentIndex |
||||||
|
}) |
||||||
|
}, 2000) |
||||||
|
|
||||||
|
chartInstance._autoTimer = timer |
||||||
|
} |
||||||
|
|
||||||
|
onMounted(() => { |
||||||
|
initChart() |
||||||
|
window.addEventListener('resize', () => { |
||||||
|
chartInstance?.resize() |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
onUnmounted(() => { |
||||||
|
if (chartInstance) { |
||||||
|
if (chartInstance._autoTimer) { |
||||||
|
clearInterval(chartInstance._autoTimer) |
||||||
|
} |
||||||
|
chartInstance.dispose() |
||||||
|
} |
||||||
|
}) |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
.channel-container { |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
height: 100%; |
||||||
|
} |
||||||
|
|
||||||
|
.stats-grid { |
||||||
|
display: grid; |
||||||
|
grid-template-columns: 1fr 1fr; |
||||||
|
gap: 8px; |
||||||
|
margin-bottom: 12px; |
||||||
|
} |
||||||
|
|
||||||
|
.stat-item { |
||||||
|
text-align: center; |
||||||
|
background: rgba(255, 255, 255, 0.02); |
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1); |
||||||
|
border-radius: 4px; |
||||||
|
padding: 8px; |
||||||
|
} |
||||||
|
|
||||||
|
.stat-label { |
||||||
|
font-size: 11px; |
||||||
|
color: #a0a8b8; |
||||||
|
margin-bottom: 4px; |
||||||
|
} |
||||||
|
|
||||||
|
.stat-value { |
||||||
|
font-size: 13px; |
||||||
|
font-weight: bold; |
||||||
|
color: #409EFF; |
||||||
|
} |
||||||
|
|
||||||
|
.chart-section { |
||||||
|
flex: 1; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 16px; |
||||||
|
} |
||||||
|
|
||||||
|
.chart { |
||||||
|
flex: 1; |
||||||
|
height: 150px; |
||||||
|
} |
||||||
|
|
||||||
|
.chart-legend { |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
gap: 8px; |
||||||
|
min-width: 80px; |
||||||
|
} |
||||||
|
|
||||||
|
.legend-item { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 6px; |
||||||
|
font-size: 11px; |
||||||
|
} |
||||||
|
|
||||||
|
.legend-dot { |
||||||
|
width: 8px; |
||||||
|
height: 8px; |
||||||
|
border-radius: 50%; |
||||||
|
flex-shrink: 0; |
||||||
|
} |
||||||
|
|
||||||
|
.legend-text { |
||||||
|
color: #a0a8b8; |
||||||
|
flex: 1; |
||||||
|
} |
||||||
|
|
||||||
|
.legend-percent { |
||||||
|
color: #409EFF; |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
</style> |
||||||
@ -0,0 +1,205 @@ |
|||||||
|
<template> |
||||||
|
<div class="scrolling-announcement"> |
||||||
|
<div class="announcement-header"> |
||||||
|
<div class="header-icon">📢</div> |
||||||
|
<div class="header-title">最新公告</div> |
||||||
|
</div> |
||||||
|
<div class="announcement-content"> |
||||||
|
<div class="scrolling-container" ref="scrollingContainer"> |
||||||
|
<div |
||||||
|
class="announcement-item current" |
||||||
|
:key="'current-' + currentIndex" |
||||||
|
> |
||||||
|
{{ announcements[currentIndex]?.content }} |
||||||
|
</div> |
||||||
|
<div |
||||||
|
class="announcement-item next" |
||||||
|
:key="'next-' + nextIndex" |
||||||
|
> |
||||||
|
{{ announcements[nextIndex]?.content }} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup> |
||||||
|
import { ref, onMounted, onUnmounted, reactive, computed } from 'vue' |
||||||
|
import { dataManager } from '../utils/dataManager.js' |
||||||
|
|
||||||
|
const scrollingContainer = ref(null) |
||||||
|
const currentIndex = ref(0) |
||||||
|
const isAnimating = ref(false) |
||||||
|
let scrollTimer = null |
||||||
|
|
||||||
|
const announcements = reactive([]) |
||||||
|
|
||||||
|
// 计算下一个索引 |
||||||
|
const nextIndex = computed(() => { |
||||||
|
return announcements.length > 0 ? (currentIndex.value + 1) % announcements.length : 0 |
||||||
|
}) |
||||||
|
|
||||||
|
// 加载公告数据 |
||||||
|
const loadAnnouncements = async () => { |
||||||
|
try { |
||||||
|
// 确保数据管理器已经加载完成 |
||||||
|
await dataManager.loadData() |
||||||
|
const data = dataManager.getData('announcements') |
||||||
|
if (data && Array.isArray(data)) { |
||||||
|
announcements.splice(0, announcements.length, ...data.map(content => ({ content }))) |
||||||
|
console.log('公告数据加载成功:', data) |
||||||
|
} else { |
||||||
|
console.warn('未找到公告数据,使用默认数据') |
||||||
|
loadDefaultAnnouncements() |
||||||
|
} |
||||||
|
} catch (error) { |
||||||
|
console.error('加载公告数据失败:', error) |
||||||
|
loadDefaultAnnouncements() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 加载默认公告 |
||||||
|
const loadDefaultAnnouncements = () => { |
||||||
|
announcements.splice(0, announcements.length, |
||||||
|
{ content: '【重要】今日牦牛交易价格上涨2.5%,请各位商户注意市场行情变化' }, |
||||||
|
{ content: '【通知】新增北京地区优质采购商,欢迎牧民朋友联系洽谈业务' }, |
||||||
|
{ content: '【提醒】本周末系统维护,交易时间调整为09:00-17:00' }, |
||||||
|
{ content: '【资讯】春季牧草丰茂,预计下月牦牛供应量将增加15%' }, |
||||||
|
{ content: '【市场】西藏地区牦牛肉品质检测合格率达98.5%' }, |
||||||
|
{ content: '【政策】政府出台新政策支持牦牛产业发展,提供资金扶持' } |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
// 执行滚动动画 |
||||||
|
const performScroll = () => { |
||||||
|
if (isAnimating.value || announcements.length <= 1) return |
||||||
|
|
||||||
|
isAnimating.value = true |
||||||
|
const container = scrollingContainer.value |
||||||
|
|
||||||
|
// 添加滚动类来触发动画 |
||||||
|
container.classList.add('scrolling') |
||||||
|
|
||||||
|
// 动画完成后更新索引并重置状态 |
||||||
|
setTimeout(() => { |
||||||
|
currentIndex.value = nextIndex.value |
||||||
|
container.classList.remove('scrolling') |
||||||
|
isAnimating.value = false |
||||||
|
}, 800) // 动画持续时间 |
||||||
|
} |
||||||
|
|
||||||
|
// 开始垂直滚动 |
||||||
|
const startVerticalScroll = () => { |
||||||
|
if (announcements.length <= 1) return |
||||||
|
|
||||||
|
scrollTimer = setInterval(() => { |
||||||
|
performScroll() |
||||||
|
}, 5000) // 每5秒切换一次 |
||||||
|
} |
||||||
|
|
||||||
|
// 停止滚动 |
||||||
|
const stopVerticalScroll = () => { |
||||||
|
if (scrollTimer) { |
||||||
|
clearInterval(scrollTimer) |
||||||
|
scrollTimer = null |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
onMounted(async () => { |
||||||
|
await loadAnnouncements() |
||||||
|
// 等待数据加载完成后开始滚动 |
||||||
|
if (announcements.length > 1) { |
||||||
|
startVerticalScroll() |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
onUnmounted(() => { |
||||||
|
stopVerticalScroll() |
||||||
|
}) |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
.scrolling-announcement { |
||||||
|
position: absolute; |
||||||
|
top: 20px; |
||||||
|
left: 20px; |
||||||
|
right: 20px; |
||||||
|
height: 50px; |
||||||
|
background: rgba(26, 31, 46, 0.95); |
||||||
|
border: 1px solid rgba(64, 158, 255, 0.4); |
||||||
|
border-radius: 8px; |
||||||
|
backdrop-filter: blur(10px); |
||||||
|
z-index: 100; |
||||||
|
overflow: hidden; |
||||||
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3); |
||||||
|
} |
||||||
|
|
||||||
|
.announcement-header { |
||||||
|
position: absolute; |
||||||
|
left: 0; |
||||||
|
top: 0; |
||||||
|
bottom: 0; |
||||||
|
width: 100px; |
||||||
|
background: rgba(64, 158, 255, 0.2); |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
gap: 6px; |
||||||
|
border-right: 1px solid rgba(64, 158, 255, 0.3); |
||||||
|
} |
||||||
|
|
||||||
|
.header-icon { |
||||||
|
font-size: 16px; |
||||||
|
} |
||||||
|
|
||||||
|
.header-title { |
||||||
|
font-size: 12px; |
||||||
|
font-weight: bold; |
||||||
|
color: #409EFF; |
||||||
|
} |
||||||
|
|
||||||
|
.announcement-content { |
||||||
|
margin-left: 100px; |
||||||
|
height: 100%; |
||||||
|
overflow: hidden; |
||||||
|
position: relative; |
||||||
|
} |
||||||
|
|
||||||
|
.scrolling-container { |
||||||
|
position: relative; |
||||||
|
height: 100%; |
||||||
|
width: 100%; |
||||||
|
} |
||||||
|
|
||||||
|
.announcement-item { |
||||||
|
position: absolute; |
||||||
|
top: 0; |
||||||
|
left: 0; |
||||||
|
right: 0; |
||||||
|
height: 100%; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
font-size: 14px; |
||||||
|
color: #fff; |
||||||
|
padding: 0 20px; |
||||||
|
white-space: nowrap; |
||||||
|
transition: transform 0.8s ease-in-out; |
||||||
|
} |
||||||
|
|
||||||
|
.announcement-item.current { |
||||||
|
transform: translateY(0); |
||||||
|
} |
||||||
|
|
||||||
|
.announcement-item.next { |
||||||
|
transform: translateY(100%); |
||||||
|
} |
||||||
|
|
||||||
|
/* 滚动动画状态 */ |
||||||
|
.scrolling-container.scrolling .announcement-item.current { |
||||||
|
transform: translateY(-100%); |
||||||
|
} |
||||||
|
|
||||||
|
.scrolling-container.scrolling .announcement-item.next { |
||||||
|
transform: translateY(0); |
||||||
|
} |
||||||
|
</style> |
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,159 @@ |
|||||||
|
<template> |
||||||
|
<BaseCard title="交易走势图"> |
||||||
|
<div ref="chartRef" class="chart"></div> |
||||||
|
</BaseCard> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup> |
||||||
|
import { ref, onMounted, onUnmounted } from 'vue' |
||||||
|
import * as echarts from 'echarts' |
||||||
|
import BaseCard from './BaseCard.vue' |
||||||
|
|
||||||
|
const chartRef = ref(null) |
||||||
|
let chartInstance = null |
||||||
|
|
||||||
|
// 模拟数据 |
||||||
|
const months = ['一月', '二月', '三月', '四月', '五月', '六月'] |
||||||
|
const salesData = [10, 20, 40, 60, 40, 80] |
||||||
|
const targetData = [15, 25, 35, 50, 45, 75] |
||||||
|
|
||||||
|
const initChart = () => { |
||||||
|
if (!chartRef.value) return |
||||||
|
|
||||||
|
chartInstance = echarts.init(chartRef.value) |
||||||
|
|
||||||
|
const option = { |
||||||
|
tooltip: { |
||||||
|
trigger: 'axis', |
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.8)', |
||||||
|
borderColor: '#409EFF', |
||||||
|
textStyle: { |
||||||
|
color: '#fff', |
||||||
|
fontSize: 12 |
||||||
|
} |
||||||
|
}, |
||||||
|
grid: { |
||||||
|
left: '8%', |
||||||
|
right: '8%', |
||||||
|
bottom: '15%', |
||||||
|
top: '10%' |
||||||
|
}, |
||||||
|
xAxis: { |
||||||
|
type: 'category', |
||||||
|
data: months, |
||||||
|
axisLine: { |
||||||
|
lineStyle: { |
||||||
|
color: '#4a5568' |
||||||
|
} |
||||||
|
}, |
||||||
|
axisTick: { |
||||||
|
show: false |
||||||
|
}, |
||||||
|
axisLabel: { |
||||||
|
color: '#a0a8b8', |
||||||
|
fontSize: 11 |
||||||
|
} |
||||||
|
}, |
||||||
|
yAxis: { |
||||||
|
type: 'value', |
||||||
|
splitLine: { |
||||||
|
lineStyle: { |
||||||
|
color: '#2d3748', |
||||||
|
type: 'dashed' |
||||||
|
} |
||||||
|
}, |
||||||
|
axisLine: { |
||||||
|
show: false |
||||||
|
}, |
||||||
|
axisTick: { |
||||||
|
show: false |
||||||
|
}, |
||||||
|
axisLabel: { |
||||||
|
color: '#a0a8b8', |
||||||
|
fontSize: 11 |
||||||
|
} |
||||||
|
}, |
||||||
|
series: [ |
||||||
|
{ |
||||||
|
name: '实际交易额', |
||||||
|
type: 'line', |
||||||
|
data: salesData, |
||||||
|
smooth: true, |
||||||
|
symbol: 'circle', |
||||||
|
symbolSize: 6, |
||||||
|
lineStyle: { |
||||||
|
color: '#409EFF', |
||||||
|
width: 3 |
||||||
|
}, |
||||||
|
itemStyle: { |
||||||
|
color: '#409EFF', |
||||||
|
borderColor: '#fff', |
||||||
|
borderWidth: 2 |
||||||
|
}, |
||||||
|
areaStyle: { |
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
||||||
|
{ offset: 0, color: 'rgba(64, 158, 255, 0.3)' }, |
||||||
|
{ offset: 1, color: 'rgba(64, 158, 255, 0.05)' } |
||||||
|
]) |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: '目标额度', |
||||||
|
type: 'line', |
||||||
|
data: targetData, |
||||||
|
smooth: true, |
||||||
|
symbol: 'circle', |
||||||
|
symbolSize: 6, |
||||||
|
lineStyle: { |
||||||
|
color: '#67C23A', |
||||||
|
width: 3 |
||||||
|
}, |
||||||
|
itemStyle: { |
||||||
|
color: '#67C23A', |
||||||
|
borderColor: '#fff', |
||||||
|
borderWidth: 2 |
||||||
|
}, |
||||||
|
areaStyle: { |
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
||||||
|
{ offset: 0, color: 'rgba(103, 194, 58, 0.3)' }, |
||||||
|
{ offset: 1, color: 'rgba(103, 194, 58, 0.05)' } |
||||||
|
]) |
||||||
|
} |
||||||
|
} |
||||||
|
] |
||||||
|
} |
||||||
|
|
||||||
|
chartInstance.setOption(option) |
||||||
|
|
||||||
|
// 添加动画效果 |
||||||
|
chartInstance.on('finished', () => { |
||||||
|
setTimeout(() => { |
||||||
|
chartInstance.dispatchAction({ |
||||||
|
type: 'showTip', |
||||||
|
seriesIndex: 0, |
||||||
|
dataIndex: salesData.length - 1 |
||||||
|
}) |
||||||
|
}, 1000) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
onMounted(() => { |
||||||
|
initChart() |
||||||
|
window.addEventListener('resize', () => { |
||||||
|
chartInstance?.resize() |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
onUnmounted(() => { |
||||||
|
if (chartInstance) { |
||||||
|
chartInstance.dispose() |
||||||
|
} |
||||||
|
}) |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
.chart { |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
} |
||||||
|
</style> |
||||||
@ -0,0 +1,337 @@ |
|||||||
|
<template> |
||||||
|
<BaseCard title="交易明细"> |
||||||
|
<div class="transaction-content"> |
||||||
|
<div class="transaction-header"> |
||||||
|
<div class="header-controls"> |
||||||
|
<select v-model="currentFilter" class="filter-select"> |
||||||
|
<option value="all">全部交易</option> |
||||||
|
<option value="today">今日交易</option> |
||||||
|
<option value="week">本周交易</option> |
||||||
|
</select> |
||||||
|
<div class="search-box"> |
||||||
|
<input v-model="searchKeyword" placeholder="搜索交易..." class="search-input"> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="summary-stats"> |
||||||
|
<div class="stat-item"> |
||||||
|
<span class="stat-label">总交易额</span> |
||||||
|
<span class="stat-value">{{ totalAmount }} 万元</span> |
||||||
|
</div> |
||||||
|
<div class="stat-item"> |
||||||
|
<span class="stat-label">交易笔数</span> |
||||||
|
<span class="stat-value">{{ totalCount }} 笔</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="transaction-table"> |
||||||
|
<div class="table-header"> |
||||||
|
<div class="th">时间</div> |
||||||
|
<div class="th">品种</div> |
||||||
|
<div class="th">数量</div> |
||||||
|
<div class="th">单价</div> |
||||||
|
<div class="th">金额</div> |
||||||
|
<div class="th">状态</div> |
||||||
|
</div> |
||||||
|
<div class="table-body"> |
||||||
|
<div |
||||||
|
v-for="transaction in filteredTransactions" |
||||||
|
:key="transaction.id" |
||||||
|
class="table-row" |
||||||
|
:class="{ 'highlight': transaction.id === highlightId }" |
||||||
|
> |
||||||
|
<div class="td time">{{ transaction.time }}</div> |
||||||
|
<div class="td category">{{ transaction.category }}</div> |
||||||
|
<div class="td quantity">{{ transaction.quantity }}</div> |
||||||
|
<div class="td price">{{ transaction.price }}</div> |
||||||
|
<div class="td amount">{{ transaction.amount }}</div> |
||||||
|
<div class="td status"> |
||||||
|
<span class="status-badge" :class="transaction.status"> |
||||||
|
{{ getStatusText(transaction.status) }} |
||||||
|
</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</BaseCard> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup> |
||||||
|
import { ref, computed, onMounted, onUnmounted } from 'vue' |
||||||
|
import BaseCard from './BaseCard.vue' |
||||||
|
|
||||||
|
const highlightIndex = ref(0) |
||||||
|
const currentIndex = ref(0) |
||||||
|
const tableBody = ref(null) |
||||||
|
|
||||||
|
// 模拟交易数据 |
||||||
|
const allTransactions = ref([ |
||||||
|
{ |
||||||
|
id: 'TXN001', |
||||||
|
category: '肉牛', |
||||||
|
quantity: 50, |
||||||
|
unitPrice: 15000, |
||||||
|
totalPrice: 75.0, |
||||||
|
buyer: '北京牧场', |
||||||
|
seller: '红原牧民', |
||||||
|
region: '北京', |
||||||
|
time: '2025/01/01 10:30', |
||||||
|
status: 'completed' |
||||||
|
}, |
||||||
|
{ |
||||||
|
id: 'TXN002', |
||||||
|
category: '奶牛', |
||||||
|
quantity: 30, |
||||||
|
unitPrice: 18000, |
||||||
|
totalPrice: 54.0, |
||||||
|
buyer: '上海乳业', |
||||||
|
seller: '高原农场', |
||||||
|
region: '上海', |
||||||
|
time: '2025/01/01 11:15', |
||||||
|
status: 'processing' |
||||||
|
}, |
||||||
|
{ |
||||||
|
id: 'TXN003', |
||||||
|
category: '黄牛', |
||||||
|
quantity: 80, |
||||||
|
unitPrice: 12000, |
||||||
|
totalPrice: 96.0, |
||||||
|
buyer: '广州食品', |
||||||
|
seller: '草原合作社', |
||||||
|
region: '广州', |
||||||
|
time: '2025/01/01 12:00', |
||||||
|
status: 'completed' |
||||||
|
}, |
||||||
|
{ |
||||||
|
id: 'TXN004', |
||||||
|
category: '水牛', |
||||||
|
quantity: 25, |
||||||
|
unitPrice: 14000, |
||||||
|
totalPrice: 35.0, |
||||||
|
buyer: '成都餐饮', |
||||||
|
seller: '藏区牧场', |
||||||
|
region: '成都', |
||||||
|
time: '2025/01/01 13:45', |
||||||
|
status: 'pending' |
||||||
|
}, |
||||||
|
{ |
||||||
|
id: 'TXN005', |
||||||
|
category: '牦牛', |
||||||
|
quantity: 40, |
||||||
|
unitPrice: 20000, |
||||||
|
totalPrice: 80.0, |
||||||
|
buyer: '西安贸易', |
||||||
|
seller: '高原牧业', |
||||||
|
region: '西安', |
||||||
|
time: '2025/01/01 14:20', |
||||||
|
status: 'completed' |
||||||
|
}, |
||||||
|
{ |
||||||
|
id: 'TXN006', |
||||||
|
category: '肉牛', |
||||||
|
quantity: 60, |
||||||
|
unitPrice: 16000, |
||||||
|
totalPrice: 96.0, |
||||||
|
buyer: '深圳集团', |
||||||
|
seller: '牧民联合社', |
||||||
|
region: '深圳', |
||||||
|
time: '2025/01/01 15:10', |
||||||
|
status: 'processing' |
||||||
|
}, |
||||||
|
{ |
||||||
|
id: 'TXN007', |
||||||
|
category: '奶牛', |
||||||
|
quantity: 35, |
||||||
|
unitPrice: 17500, |
||||||
|
totalPrice: 61.25, |
||||||
|
buyer: '杭州乳制品', |
||||||
|
seller: '草原农场', |
||||||
|
region: '杭州', |
||||||
|
time: '2025/01/01 16:30', |
||||||
|
status: 'completed' |
||||||
|
}, |
||||||
|
{ |
||||||
|
id: 'TXN008', |
||||||
|
category: '黄牛', |
||||||
|
quantity: 70, |
||||||
|
unitPrice: 13000, |
||||||
|
totalPrice: 91.0, |
||||||
|
buyer: '重庆食品', |
||||||
|
seller: '高原牧场', |
||||||
|
region: '重庆', |
||||||
|
time: '2025/01/01 17:15', |
||||||
|
status: 'pending' |
||||||
|
} |
||||||
|
]) |
||||||
|
|
||||||
|
// 显示的交易记录(滚动显示) |
||||||
|
const visibleTransactions = computed(() => { |
||||||
|
const itemsToShow = 6 |
||||||
|
const result = [] |
||||||
|
for (let i = 0; i < itemsToShow; i++) { |
||||||
|
const index = (currentIndex.value + i) % allTransactions.value.length |
||||||
|
result.push(allTransactions.value[index]) |
||||||
|
} |
||||||
|
return result |
||||||
|
}) |
||||||
|
|
||||||
|
const getStatusText = (status) => { |
||||||
|
const statusMap = { |
||||||
|
completed: '已完成', |
||||||
|
processing: '处理中', |
||||||
|
pending: '待处理' |
||||||
|
} |
||||||
|
return statusMap[status] || '未知' |
||||||
|
} |
||||||
|
|
||||||
|
// 自动滚动效果 |
||||||
|
let scrollTimer = null |
||||||
|
let highlightTimer = null |
||||||
|
|
||||||
|
onMounted(() => { |
||||||
|
// 数据滚动 |
||||||
|
scrollTimer = setInterval(() => { |
||||||
|
currentIndex.value = (currentIndex.value + 1) % allTransactions.value.length |
||||||
|
}, 3000) |
||||||
|
|
||||||
|
// 行高亮 |
||||||
|
highlightTimer = setInterval(() => { |
||||||
|
highlightIndex.value = (highlightIndex.value + 1) % 6 |
||||||
|
}, 1000) |
||||||
|
}) |
||||||
|
|
||||||
|
onUnmounted(() => { |
||||||
|
if (scrollTimer) { |
||||||
|
clearInterval(scrollTimer) |
||||||
|
} |
||||||
|
if (highlightTimer) { |
||||||
|
clearInterval(highlightTimer) |
||||||
|
} |
||||||
|
}) |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
.transaction-table { |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
background: rgba(255, 255, 255, 0.02); |
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1); |
||||||
|
border-radius: 8px; |
||||||
|
overflow: hidden; |
||||||
|
} |
||||||
|
|
||||||
|
.table-header { |
||||||
|
display: grid; |
||||||
|
grid-template-columns: 80px 80px 80px 80px 90px 100px 100px 80px 120px 80px; |
||||||
|
background: rgba(64, 158, 255, 0.1); |
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.2); |
||||||
|
} |
||||||
|
|
||||||
|
.header-cell { |
||||||
|
padding: 12px 8px; |
||||||
|
font-size: 12px; |
||||||
|
font-weight: bold; |
||||||
|
color: #409EFF; |
||||||
|
text-align: center; |
||||||
|
border-right: 1px solid rgba(255, 255, 255, 0.1); |
||||||
|
} |
||||||
|
|
||||||
|
.header-cell:last-child { |
||||||
|
border-right: none; |
||||||
|
} |
||||||
|
|
||||||
|
.table-body { |
||||||
|
flex: 1; |
||||||
|
overflow: hidden; |
||||||
|
} |
||||||
|
|
||||||
|
.table-row { |
||||||
|
display: grid; |
||||||
|
grid-template-columns: 80px 80px 80px 80px 90px 100px 100px 80px 120px 80px; |
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.05); |
||||||
|
transition: all 0.3s ease; |
||||||
|
} |
||||||
|
|
||||||
|
.table-row:hover, |
||||||
|
.table-row.highlight { |
||||||
|
background: rgba(64, 158, 255, 0.1); |
||||||
|
} |
||||||
|
|
||||||
|
.table-cell { |
||||||
|
padding: 10px 8px; |
||||||
|
font-size: 11px; |
||||||
|
color: #a0a8b8; |
||||||
|
text-align: center; |
||||||
|
border-right: 1px solid rgba(255, 255, 255, 0.05); |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
overflow: hidden; |
||||||
|
text-overflow: ellipsis; |
||||||
|
white-space: nowrap; |
||||||
|
} |
||||||
|
|
||||||
|
.table-cell:last-child { |
||||||
|
border-right: none; |
||||||
|
} |
||||||
|
|
||||||
|
.table-cell.quantity { |
||||||
|
color: #67C23A; |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
|
||||||
|
.table-cell.price { |
||||||
|
color: #409EFF; |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
|
||||||
|
.table-cell.total { |
||||||
|
color: #E6A23C; |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
|
||||||
|
.table-cell.time { |
||||||
|
color: #8B5CF6; |
||||||
|
font-size: 10px; |
||||||
|
} |
||||||
|
|
||||||
|
.status { |
||||||
|
padding: 3px 8px; |
||||||
|
border-radius: 4px; |
||||||
|
font-size: 10px; |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
|
||||||
|
.status.completed { |
||||||
|
background: rgba(103, 194, 58, 0.2); |
||||||
|
color: #67C23A; |
||||||
|
} |
||||||
|
|
||||||
|
.status.processing { |
||||||
|
background: rgba(230, 162, 60, 0.2); |
||||||
|
color: #E6A23C; |
||||||
|
} |
||||||
|
|
||||||
|
.status.pending { |
||||||
|
background: rgba(245, 108, 108, 0.2); |
||||||
|
color: #F56C6C; |
||||||
|
} |
||||||
|
|
||||||
|
/* 动画效果 */ |
||||||
|
@keyframes slideUp { |
||||||
|
from { |
||||||
|
transform: translateY(20px); |
||||||
|
opacity: 0; |
||||||
|
} |
||||||
|
to { |
||||||
|
transform: translateY(0); |
||||||
|
opacity: 1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.table-row { |
||||||
|
animation: slideUp 0.5s ease; |
||||||
|
} |
||||||
|
</style> |
||||||
@ -0,0 +1,172 @@ |
|||||||
|
<template> |
||||||
|
<BaseCard title="活牛/鲜肉价格趋势"> |
||||||
|
<div ref="chartRef" class="chart"></div> |
||||||
|
</BaseCard> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup> |
||||||
|
import { ref, onMounted, onUnmounted } from 'vue' |
||||||
|
import * as echarts from 'echarts' |
||||||
|
import BaseCard from './BaseCard.vue' |
||||||
|
|
||||||
|
const chartRef = ref(null) |
||||||
|
let chartInstance = null |
||||||
|
|
||||||
|
// 模拟数据 - 根据2024年实际市场价格调整 |
||||||
|
const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月'] |
||||||
|
const liveCattleData = [26.5, 27.2, 28.8, 28.3, 29.1, 30.2, 29.6, 30.8] // 活牛价格 元/公斤 (实际价格范围26-31元) |
||||||
|
const beefData = [62.8, 61.5, 60.2, 58.9, 59.8, 61.2, 63.5, 64.8] // 牛肉价格 元/公斤 (实际价格范围58-65元) |
||||||
|
|
||||||
|
const initChart = () => { |
||||||
|
if (!chartRef.value) return |
||||||
|
|
||||||
|
chartInstance = echarts.init(chartRef.value) |
||||||
|
|
||||||
|
const option = { |
||||||
|
tooltip: { |
||||||
|
trigger: 'axis', |
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.8)', |
||||||
|
borderColor: '#409EFF', |
||||||
|
textStyle: { |
||||||
|
color: '#fff', |
||||||
|
fontSize: 12 |
||||||
|
}, |
||||||
|
formatter: function(params) { |
||||||
|
let result = `${params[0].axisValue}<br/>` |
||||||
|
params.forEach(param => { |
||||||
|
result += `${param.marker}${param.seriesName}: ${param.value}元/公斤<br/>` |
||||||
|
}) |
||||||
|
return result |
||||||
|
} |
||||||
|
}, |
||||||
|
grid: { |
||||||
|
left: '8%', |
||||||
|
right: '8%', |
||||||
|
bottom: '15%', |
||||||
|
top: '15%' |
||||||
|
}, |
||||||
|
legend: { |
||||||
|
data: ['活牛价格', '牛肉价格'], |
||||||
|
textStyle: { |
||||||
|
color: '#a0a8b8', |
||||||
|
fontSize: 11 |
||||||
|
}, |
||||||
|
top: '5%' |
||||||
|
}, |
||||||
|
xAxis: { |
||||||
|
type: 'category', |
||||||
|
data: months, |
||||||
|
axisLine: { |
||||||
|
lineStyle: { |
||||||
|
color: '#4a5568' |
||||||
|
} |
||||||
|
}, |
||||||
|
axisTick: { |
||||||
|
show: false |
||||||
|
}, |
||||||
|
axisLabel: { |
||||||
|
color: '#a0a8b8', |
||||||
|
fontSize: 10 |
||||||
|
} |
||||||
|
}, |
||||||
|
yAxis: { |
||||||
|
type: 'value', |
||||||
|
name: '价格(元/公斤)', |
||||||
|
splitLine: { |
||||||
|
lineStyle: { |
||||||
|
color: '#2d3748', |
||||||
|
type: 'dashed' |
||||||
|
} |
||||||
|
}, |
||||||
|
axisLine: { |
||||||
|
show: false |
||||||
|
}, |
||||||
|
axisTick: { |
||||||
|
show: false |
||||||
|
}, |
||||||
|
axisLabel: { |
||||||
|
color: '#a0a8b8', |
||||||
|
fontSize: 10 |
||||||
|
}, |
||||||
|
nameTextStyle: { |
||||||
|
color: '#a0a8b8', |
||||||
|
fontSize: 10 |
||||||
|
} |
||||||
|
}, |
||||||
|
series: [ |
||||||
|
{ |
||||||
|
name: '活牛价格', |
||||||
|
type: 'line', |
||||||
|
data: liveCattleData, |
||||||
|
smooth: true, |
||||||
|
symbol: 'circle', |
||||||
|
symbolSize: 6, |
||||||
|
lineStyle: { |
||||||
|
color: '#67C23A', |
||||||
|
width: 3 |
||||||
|
}, |
||||||
|
itemStyle: { |
||||||
|
color: '#67C23A', |
||||||
|
borderColor: '#fff', |
||||||
|
borderWidth: 2 |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: '牛肉价格', |
||||||
|
type: 'line', |
||||||
|
data: beefData, |
||||||
|
smooth: true, |
||||||
|
symbol: 'circle', |
||||||
|
symbolSize: 6, |
||||||
|
lineStyle: { |
||||||
|
color: '#E6A23C', |
||||||
|
width: 3 |
||||||
|
}, |
||||||
|
itemStyle: { |
||||||
|
color: '#E6A23C', |
||||||
|
borderColor: '#fff', |
||||||
|
borderWidth: 2 |
||||||
|
} |
||||||
|
} |
||||||
|
] |
||||||
|
} |
||||||
|
|
||||||
|
chartInstance.setOption(option) |
||||||
|
|
||||||
|
// 添加动画效果 |
||||||
|
let currentIndex = 0 |
||||||
|
const timer = setInterval(() => { |
||||||
|
chartInstance.dispatchAction({ |
||||||
|
type: 'showTip', |
||||||
|
seriesIndex: [0, 1], |
||||||
|
dataIndex: currentIndex |
||||||
|
}) |
||||||
|
currentIndex = (currentIndex + 1) % months.length |
||||||
|
}, 2500) |
||||||
|
|
||||||
|
chartInstance._autoTimer = timer |
||||||
|
} |
||||||
|
|
||||||
|
onMounted(() => { |
||||||
|
initChart() |
||||||
|
window.addEventListener('resize', () => { |
||||||
|
chartInstance?.resize() |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
onUnmounted(() => { |
||||||
|
if (chartInstance) { |
||||||
|
if (chartInstance._autoTimer) { |
||||||
|
clearInterval(chartInstance._autoTimer) |
||||||
|
} |
||||||
|
chartInstance.dispose() |
||||||
|
} |
||||||
|
}) |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
.chart { |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
} |
||||||
|
</style> |
||||||
@ -0,0 +1,296 @@ |
|||||||
|
<template> |
||||||
|
<BaseCard title="牦牛销售类型统计"> |
||||||
|
<div class="sales-container"> |
||||||
|
<div class="chart-wrapper"> |
||||||
|
<div ref="chartRef" class="chart"></div> |
||||||
|
</div> |
||||||
|
<div class="legend-container"> |
||||||
|
<div v-for="item in salesData" :key="item.name" class="legend-item"> |
||||||
|
<span class="legend-dot" :style="{ backgroundColor: item.color }"></span> |
||||||
|
<div class="legend-info"> |
||||||
|
<span class="legend-text">{{ item.name }}</span> |
||||||
|
<div class="legend-stats"> |
||||||
|
<span class="legend-count">{{ item.count }}头</span> |
||||||
|
<span class="legend-percent">{{ item.value }}%</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</BaseCard> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup> |
||||||
|
import { ref, onMounted, onUnmounted } from 'vue' |
||||||
|
import * as echarts from 'echarts' |
||||||
|
import BaseCard from './BaseCard.vue' |
||||||
|
|
||||||
|
const chartRef = ref(null) |
||||||
|
let chartInstance = null |
||||||
|
|
||||||
|
// 销售类型数据(屠宰、养殖、其他) |
||||||
|
const salesData = ref([ |
||||||
|
{ |
||||||
|
name: '屠宰用途', |
||||||
|
value: 52, |
||||||
|
count: 1456, |
||||||
|
color: '#E6A23C', |
||||||
|
description: '用于屠宰加工的牦牛' |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: '养殖用途', |
||||||
|
value: 35, |
||||||
|
count: 980, |
||||||
|
color: '#67C23A', |
||||||
|
description: '用于继续养殖的牦牛' |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: '其他用途', |
||||||
|
value: 13, |
||||||
|
count: 364, |
||||||
|
color: '#409EFF', |
||||||
|
description: '其他特殊用途的牦牛' |
||||||
|
} |
||||||
|
]) |
||||||
|
|
||||||
|
// 数据更新函数 |
||||||
|
const updateData = () => { |
||||||
|
salesData.value.forEach(item => { |
||||||
|
// 小幅度随机调整数据,保持总和为100% |
||||||
|
const variance = (Math.random() - 0.5) * 4 // ±2%的变化 |
||||||
|
item.value = Math.max(5, Math.min(80, item.value + variance)) |
||||||
|
|
||||||
|
// 更新对应的数量 |
||||||
|
const totalCount = 2800 // 假设总数为2800头 |
||||||
|
item.count = Math.floor((item.value / 100) * totalCount) |
||||||
|
}) |
||||||
|
|
||||||
|
// 确保百分比总和为100% |
||||||
|
const totalPercent = salesData.value.reduce((sum, item) => sum + item.value, 0) |
||||||
|
salesData.value.forEach(item => { |
||||||
|
item.value = Math.round((item.value / totalPercent) * 100) |
||||||
|
}) |
||||||
|
|
||||||
|
// 重新计算数量以匹配调整后的百分比 |
||||||
|
const totalCount = 2800 |
||||||
|
salesData.value.forEach(item => { |
||||||
|
item.count = Math.floor((item.value / 100) * totalCount) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const initChart = () => { |
||||||
|
if (!chartRef.value) return |
||||||
|
|
||||||
|
chartInstance = echarts.init(chartRef.value) |
||||||
|
|
||||||
|
const updateChart = () => { |
||||||
|
const option = { |
||||||
|
tooltip: { |
||||||
|
trigger: 'item', |
||||||
|
backgroundColor: 'rgba(15, 25, 45, 0.95)', |
||||||
|
borderColor: '#00d4ff', |
||||||
|
borderWidth: 1, |
||||||
|
textStyle: { |
||||||
|
color: '#fff', |
||||||
|
fontSize: 13 |
||||||
|
}, |
||||||
|
formatter: function(params) { |
||||||
|
const item = salesData.value.find(d => d.name === params.name) |
||||||
|
return `<div style="padding: 8px;"> |
||||||
|
<strong style="color: #00d4ff;">${params.name}</strong><br/> |
||||||
|
<span style="color: #67c23a;">数量: ${item.count} 头</span><br/> |
||||||
|
<span style="color: #8cc8ff;">占比: ${params.percent}%</span><br/> |
||||||
|
<span style="color: #a0a8b8;">${item.description}</span> |
||||||
|
</div>` |
||||||
|
} |
||||||
|
}, |
||||||
|
series: [{ |
||||||
|
name: '销售类型', |
||||||
|
type: 'pie', |
||||||
|
radius: ['35%', '75%'], |
||||||
|
center: ['50%', '50%'], |
||||||
|
data: salesData.value.map(item => ({ |
||||||
|
name: item.name, |
||||||
|
value: item.value, |
||||||
|
itemStyle: { |
||||||
|
color: item.color, |
||||||
|
borderRadius: 8, |
||||||
|
borderColor: '#1a1f2e', |
||||||
|
borderWidth: 3, |
||||||
|
shadowBlur: 10, |
||||||
|
shadowColor: 'rgba(0, 0, 0, 0.3)' |
||||||
|
} |
||||||
|
})), |
||||||
|
emphasis: { |
||||||
|
itemStyle: { |
||||||
|
shadowBlur: 20, |
||||||
|
shadowOffsetX: 0, |
||||||
|
shadowColor: 'rgba(0, 212, 255, 0.5)', |
||||||
|
borderColor: '#00d4ff', |
||||||
|
borderWidth: 3 |
||||||
|
}, |
||||||
|
scaleSize: 10 |
||||||
|
}, |
||||||
|
labelLine: { |
||||||
|
show: false |
||||||
|
}, |
||||||
|
label: { |
||||||
|
show: true, |
||||||
|
position: 'center', |
||||||
|
fontSize: 14, |
||||||
|
fontWeight: 'bold', |
||||||
|
color: '#fff', |
||||||
|
formatter: function(params) { |
||||||
|
if (params.name === '屠宰用途') { |
||||||
|
return `${params.percent}%\n${params.name}` |
||||||
|
} |
||||||
|
return '' |
||||||
|
} |
||||||
|
}, |
||||||
|
animationType: 'scale', |
||||||
|
animationEasing: 'elasticOut', |
||||||
|
animationDelay: function (idx) { |
||||||
|
return Math.random() * 200 |
||||||
|
} |
||||||
|
}] |
||||||
|
} |
||||||
|
chartInstance.setOption(option) |
||||||
|
} |
||||||
|
|
||||||
|
updateChart() |
||||||
|
|
||||||
|
// 自动高亮效果 |
||||||
|
let currentIndex = 0 |
||||||
|
const timer = setInterval(() => { |
||||||
|
chartInstance.dispatchAction({ |
||||||
|
type: 'downplay', |
||||||
|
seriesIndex: 0, |
||||||
|
dataIndex: currentIndex |
||||||
|
}) |
||||||
|
currentIndex = (currentIndex + 1) % salesData.value.length |
||||||
|
chartInstance.dispatchAction({ |
||||||
|
type: 'highlight', |
||||||
|
seriesIndex: 0, |
||||||
|
dataIndex: currentIndex |
||||||
|
}) |
||||||
|
}, 3000) |
||||||
|
|
||||||
|
// 定期更新数据 |
||||||
|
const dataTimer = setInterval(() => { |
||||||
|
updateData() |
||||||
|
updateChart() |
||||||
|
}, 10000) // 10秒更新一次 |
||||||
|
|
||||||
|
chartInstance._autoTimer = timer |
||||||
|
chartInstance._dataTimer = dataTimer |
||||||
|
} |
||||||
|
|
||||||
|
onMounted(() => { |
||||||
|
initChart() |
||||||
|
window.addEventListener('resize', () => { |
||||||
|
chartInstance?.resize() |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
onUnmounted(() => { |
||||||
|
if (chartInstance) { |
||||||
|
if (chartInstance._autoTimer) { |
||||||
|
clearInterval(chartInstance._autoTimer) |
||||||
|
} |
||||||
|
if (chartInstance._dataTimer) { |
||||||
|
clearInterval(chartInstance._dataTimer) |
||||||
|
} |
||||||
|
chartInstance.dispose() |
||||||
|
} |
||||||
|
}) |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
.sales-container { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
height: 100%; |
||||||
|
gap: 20px; |
||||||
|
padding: 10px; |
||||||
|
} |
||||||
|
|
||||||
|
.chart-wrapper { |
||||||
|
flex: 1; |
||||||
|
height: 100%; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
} |
||||||
|
|
||||||
|
.chart { |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
min-height: 200px; |
||||||
|
} |
||||||
|
|
||||||
|
.legend-container { |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
gap: 16px; |
||||||
|
min-width: 140px; |
||||||
|
max-width: 160px; |
||||||
|
} |
||||||
|
|
||||||
|
.legend-item { |
||||||
|
display: flex; |
||||||
|
align-items: flex-start; |
||||||
|
gap: 12px; |
||||||
|
padding: 8px; |
||||||
|
border-radius: 6px; |
||||||
|
background: rgba(255, 255, 255, 0.02); |
||||||
|
border: 1px solid rgba(255, 255, 255, 0.05); |
||||||
|
transition: all 0.3s ease; |
||||||
|
} |
||||||
|
|
||||||
|
.legend-item:hover { |
||||||
|
background: rgba(255, 255, 255, 0.05); |
||||||
|
border-color: rgba(64, 158, 255, 0.2); |
||||||
|
transform: translateX(2px); |
||||||
|
} |
||||||
|
|
||||||
|
.legend-dot { |
||||||
|
width: 12px; |
||||||
|
height: 12px; |
||||||
|
border-radius: 50%; |
||||||
|
flex-shrink: 0; |
||||||
|
margin-top: 2px; |
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); |
||||||
|
} |
||||||
|
|
||||||
|
.legend-info { |
||||||
|
flex: 1; |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
gap: 4px; |
||||||
|
} |
||||||
|
|
||||||
|
.legend-text { |
||||||
|
color: #ffffff; |
||||||
|
font-size: 12px; |
||||||
|
font-weight: 500; |
||||||
|
line-height: 1.2; |
||||||
|
} |
||||||
|
|
||||||
|
.legend-stats { |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
gap: 2px; |
||||||
|
} |
||||||
|
|
||||||
|
.legend-count { |
||||||
|
color: #8cc8ff; |
||||||
|
font-size: 11px; |
||||||
|
font-weight: 600; |
||||||
|
} |
||||||
|
|
||||||
|
.legend-percent { |
||||||
|
color: #00d4ff; |
||||||
|
font-size: 14px; |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
</style> |
||||||
@ -0,0 +1,313 @@ |
|||||||
|
<template> |
||||||
|
<BaseCard title="交易所牦牛成交数据"> |
||||||
|
<div class="time-selector"> |
||||||
|
<div class="selector-buttons"> |
||||||
|
<button |
||||||
|
v-for="period in timePeriods" |
||||||
|
:key="period.value" |
||||||
|
:class="['period-btn', { active: selectedPeriod === period.value }]" |
||||||
|
@click="changePeriod(period.value)" |
||||||
|
> |
||||||
|
{{ period.label }} |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div ref="chartRef" class="chart"></div> |
||||||
|
</BaseCard> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup> |
||||||
|
import { ref, onMounted, onUnmounted, watch } from 'vue' |
||||||
|
import * as echarts from 'echarts' |
||||||
|
import BaseCard from './BaseCard.vue' |
||||||
|
|
||||||
|
const chartRef = ref(null) |
||||||
|
let chartInstance = null |
||||||
|
|
||||||
|
const selectedPeriod = ref('day') |
||||||
|
const timePeriods = [ |
||||||
|
{ label: '日', value: 'day' }, |
||||||
|
{ label: '周', value: 'week' }, |
||||||
|
{ label: '月', value: 'month' } |
||||||
|
] |
||||||
|
|
||||||
|
// 生成时间标签 |
||||||
|
const generateTimeLabels = (period) => { |
||||||
|
const now = new Date() |
||||||
|
const labels = [] |
||||||
|
|
||||||
|
if (period === 'day') { |
||||||
|
// 过去6天 |
||||||
|
for (let i = 5; i >= 0; i--) { |
||||||
|
const date = new Date(now) |
||||||
|
date.setDate(date.getDate() - i) |
||||||
|
labels.push(`${date.getMonth() + 1}/${date.getDate()}`) |
||||||
|
} |
||||||
|
} else if (period === 'week') { |
||||||
|
// 过去6周 |
||||||
|
for (let i = 5; i >= 0; i--) { |
||||||
|
const date = new Date(now) |
||||||
|
date.setDate(date.getDate() - i * 7) |
||||||
|
const weekStart = new Date(date) |
||||||
|
const weekEnd = new Date(date) |
||||||
|
weekEnd.setDate(weekEnd.getDate() + 6) |
||||||
|
labels.push(`${weekStart.getMonth() + 1}/${weekStart.getDate()}-${weekEnd.getMonth() + 1}/${weekEnd.getDate()}`) |
||||||
|
} |
||||||
|
} else if (period === 'month') { |
||||||
|
// 过去6个月 |
||||||
|
for (let i = 5; i >= 0; i--) { |
||||||
|
const date = new Date(now) |
||||||
|
date.setMonth(date.getMonth() - i) |
||||||
|
labels.push(`${date.getFullYear()}/${date.getMonth() + 1}`) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return labels |
||||||
|
} |
||||||
|
|
||||||
|
// 生成模拟数据 |
||||||
|
const generateMockData = (period) => { |
||||||
|
const baseTrading = period === 'day' ? 150 : period === 'week' ? 800 : 3500 |
||||||
|
const baseOrders = period === 'day' ? 45 : period === 'week' ? 280 : 1200 |
||||||
|
|
||||||
|
const tradingData = [] |
||||||
|
const orderData = [] |
||||||
|
|
||||||
|
for (let i = 0; i < 6; i++) { |
||||||
|
const tradingVariation = Math.floor(Math.random() * 200) - 100 |
||||||
|
const orderVariation = Math.floor(Math.random() * 60) - 30 |
||||||
|
|
||||||
|
tradingData.push(baseTrading + tradingVariation) |
||||||
|
orderData.push(baseOrders + orderVariation) |
||||||
|
} |
||||||
|
|
||||||
|
return { tradingData, orderData } |
||||||
|
} |
||||||
|
|
||||||
|
const initChart = () => { |
||||||
|
if (!chartRef.value) return |
||||||
|
|
||||||
|
chartInstance = echarts.init(chartRef.value) |
||||||
|
updateChart() |
||||||
|
} |
||||||
|
|
||||||
|
const updateChart = () => { |
||||||
|
if (!chartInstance) return |
||||||
|
|
||||||
|
const timeLabels = generateTimeLabels(selectedPeriod.value) |
||||||
|
const { tradingData, orderData } = generateMockData(selectedPeriod.value) |
||||||
|
|
||||||
|
const option = { |
||||||
|
tooltip: { |
||||||
|
trigger: 'axis', |
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.8)', |
||||||
|
borderColor: '#409EFF', |
||||||
|
textStyle: { |
||||||
|
color: '#fff', |
||||||
|
fontSize: 12 |
||||||
|
}, |
||||||
|
formatter: function(params) { |
||||||
|
let result = `${params[0].axisValue}<br/>` |
||||||
|
params.forEach(param => { |
||||||
|
const unit = param.seriesName === '牦牛交易数量' ? '头' : '单' |
||||||
|
result += `${param.marker}${param.seriesName}: ${param.value}${unit}<br/>` |
||||||
|
}) |
||||||
|
return result |
||||||
|
} |
||||||
|
}, |
||||||
|
grid: { |
||||||
|
left: '8%', |
||||||
|
right: '8%', |
||||||
|
bottom: '15%', |
||||||
|
top: '20%' |
||||||
|
}, |
||||||
|
legend: { |
||||||
|
data: ['牦牛交易数量', '成交订单数量'], |
||||||
|
textStyle: { |
||||||
|
color: '#a0a8b8', |
||||||
|
fontSize: 11 |
||||||
|
}, |
||||||
|
top: '8%' |
||||||
|
}, |
||||||
|
xAxis: { |
||||||
|
type: 'category', |
||||||
|
data: timeLabels, |
||||||
|
axisLine: { |
||||||
|
lineStyle: { |
||||||
|
color: '#4a5568' |
||||||
|
} |
||||||
|
}, |
||||||
|
axisTick: { |
||||||
|
show: false |
||||||
|
}, |
||||||
|
axisLabel: { |
||||||
|
color: '#a0a8b8', |
||||||
|
fontSize: 10, |
||||||
|
rotate: selectedPeriod.value === 'week' ? 45 : 0 |
||||||
|
} |
||||||
|
}, |
||||||
|
yAxis: [ |
||||||
|
{ |
||||||
|
type: 'value', |
||||||
|
name: '交易数量(头)', |
||||||
|
position: 'left', |
||||||
|
splitLine: { |
||||||
|
lineStyle: { |
||||||
|
color: '#2d3748', |
||||||
|
type: 'dashed' |
||||||
|
} |
||||||
|
}, |
||||||
|
axisLine: { |
||||||
|
show: false |
||||||
|
}, |
||||||
|
axisTick: { |
||||||
|
show: false |
||||||
|
}, |
||||||
|
axisLabel: { |
||||||
|
color: '#a0a8b8', |
||||||
|
fontSize: 10 |
||||||
|
}, |
||||||
|
nameTextStyle: { |
||||||
|
color: '#a0a8b8', |
||||||
|
fontSize: 10 |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: 'value', |
||||||
|
name: '订单数量(单)', |
||||||
|
position: 'right', |
||||||
|
splitLine: { |
||||||
|
show: false |
||||||
|
}, |
||||||
|
axisLine: { |
||||||
|
show: false |
||||||
|
}, |
||||||
|
axisTick: { |
||||||
|
show: false |
||||||
|
}, |
||||||
|
axisLabel: { |
||||||
|
color: '#a0a8b8', |
||||||
|
fontSize: 10 |
||||||
|
}, |
||||||
|
nameTextStyle: { |
||||||
|
color: '#a0a8b8', |
||||||
|
fontSize: 10 |
||||||
|
} |
||||||
|
} |
||||||
|
], |
||||||
|
series: [ |
||||||
|
{ |
||||||
|
name: '牦牛交易数量', |
||||||
|
type: 'line', |
||||||
|
yAxisIndex: 0, |
||||||
|
data: tradingData, |
||||||
|
smooth: true, |
||||||
|
symbol: 'circle', |
||||||
|
symbolSize: 6, |
||||||
|
lineStyle: { |
||||||
|
color: '#409EFF', |
||||||
|
width: 3 |
||||||
|
}, |
||||||
|
itemStyle: { |
||||||
|
color: '#409EFF', |
||||||
|
borderColor: '#fff', |
||||||
|
borderWidth: 2 |
||||||
|
}, |
||||||
|
// areaStyle: { |
||||||
|
// color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
||||||
|
// { offset: 0, color: 'rgba(64, 158, 255, 0.3)' }, |
||||||
|
// { offset: 1, color: 'rgba(64, 158, 255, 0.05)' } |
||||||
|
// ]) |
||||||
|
// } |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: '成交订单数量', |
||||||
|
type: 'line', |
||||||
|
yAxisIndex: 1, |
||||||
|
data: orderData, |
||||||
|
smooth: true, |
||||||
|
symbol: 'circle', |
||||||
|
symbolSize: 6, |
||||||
|
lineStyle: { |
||||||
|
color: '#67C23A', |
||||||
|
width: 3 |
||||||
|
}, |
||||||
|
itemStyle: { |
||||||
|
color: '#67C23A', |
||||||
|
borderColor: '#fff', |
||||||
|
borderWidth: 2 |
||||||
|
}, |
||||||
|
// areaStyle: { |
||||||
|
// color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
||||||
|
// { offset: 0, color: 'rgba(103, 194, 58, 0.2)' }, |
||||||
|
// { offset: 1, color: 'rgba(103, 194, 58, 0.05)' } |
||||||
|
// ]) |
||||||
|
// } |
||||||
|
} |
||||||
|
] |
||||||
|
} |
||||||
|
|
||||||
|
chartInstance.setOption(option, true) |
||||||
|
} |
||||||
|
|
||||||
|
const changePeriod = (period) => { |
||||||
|
selectedPeriod.value = period |
||||||
|
} |
||||||
|
|
||||||
|
// 监听时间周期变化 |
||||||
|
watch(selectedPeriod, () => { |
||||||
|
updateChart() |
||||||
|
}) |
||||||
|
|
||||||
|
onMounted(() => { |
||||||
|
initChart() |
||||||
|
window.addEventListener('resize', () => { |
||||||
|
chartInstance?.resize() |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
onUnmounted(() => { |
||||||
|
if (chartInstance) { |
||||||
|
chartInstance.dispose() |
||||||
|
} |
||||||
|
}) |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
.time-selector { |
||||||
|
padding: 0 20px 15px; |
||||||
|
} |
||||||
|
|
||||||
|
.selector-buttons { |
||||||
|
display: flex; |
||||||
|
gap: 8px; |
||||||
|
justify-content: center; |
||||||
|
} |
||||||
|
|
||||||
|
.period-btn { |
||||||
|
padding: 6px 16px; |
||||||
|
border: 1px solid #4a5568; |
||||||
|
background: transparent; |
||||||
|
color: #a0a8b8; |
||||||
|
border-radius: 4px; |
||||||
|
cursor: pointer; |
||||||
|
font-size: 12px; |
||||||
|
transition: all 0.3s ease; |
||||||
|
} |
||||||
|
|
||||||
|
.period-btn:hover { |
||||||
|
border-color: #409EFF; |
||||||
|
color: #409EFF; |
||||||
|
} |
||||||
|
|
||||||
|
.period-btn.active { |
||||||
|
border-color: #409EFF; |
||||||
|
background: #409EFF; |
||||||
|
color: #fff; |
||||||
|
} |
||||||
|
|
||||||
|
.chart { |
||||||
|
width: 100%; |
||||||
|
height: calc(100% - 60px); |
||||||
|
} |
||||||
|
</style> |
||||||
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 38 KiB |
@ -0,0 +1,5 @@ |
|||||||
|
import { createApp } from 'vue' |
||||||
|
import App from './App.vue' |
||||||
|
import './styles/index.scss' |
||||||
|
|
||||||
|
createApp(App).mount('#app')
|
||||||
@ -0,0 +1,166 @@ |
|||||||
|
// CSS变量定义 |
||||||
|
:root { |
||||||
|
--screen-width: 5120px; |
||||||
|
--screen-height: 1440px; |
||||||
|
} |
||||||
|
|
||||||
|
* { |
||||||
|
margin: 0; |
||||||
|
padding: 0; |
||||||
|
box-sizing: border-box; |
||||||
|
} |
||||||
|
|
||||||
|
html, body { |
||||||
|
width: 100%; |
||||||
|
height: 100vh; |
||||||
|
overflow: auto; |
||||||
|
font-family: 'Microsoft YaHei', 'PingFang SC', 'Helvetica Neue', Arial, sans-serif; |
||||||
|
background: $bg-primary; |
||||||
|
color: $text-primary; |
||||||
|
user-select: none; |
||||||
|
margin: 0; |
||||||
|
padding: 0; |
||||||
|
} |
||||||
|
|
||||||
|
#app { |
||||||
|
width: var(--screen-width); |
||||||
|
height: var(--screen-height); |
||||||
|
min-width: var(--screen-width); |
||||||
|
min-height: var(--screen-height); |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
position: relative; |
||||||
|
} |
||||||
|
|
||||||
|
// 固定尺寸布局 - 使用flex布局 |
||||||
|
.dashboard-container { |
||||||
|
width: var(--screen-width); |
||||||
|
height: var(--screen-height); |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
padding: 16px; |
||||||
|
background: radial-gradient(ellipse at center, rgba(64, 158, 255, 0.1) 0%, $bg-primary 70%); |
||||||
|
overflow: hidden; |
||||||
|
box-sizing: border-box; |
||||||
|
gap: 16px; |
||||||
|
|
||||||
|
&::before { |
||||||
|
content: ''; |
||||||
|
position: absolute; |
||||||
|
top: 0; |
||||||
|
left: 0; |
||||||
|
right: 0; |
||||||
|
bottom: 0; |
||||||
|
background: |
||||||
|
linear-gradient(90deg, transparent 0%, rgba(64, 158, 255, 0.02) 50%, transparent 100%), |
||||||
|
linear-gradient(0deg, transparent 0%, rgba(139, 92, 246, 0.02) 50%, transparent 100%); |
||||||
|
pointer-events: none; |
||||||
|
z-index: 1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 卡片样式现在由BaseCard组件统一管理 |
||||||
|
|
||||||
|
// 数据显示样式 |
||||||
|
.data-item { |
||||||
|
display: flex; |
||||||
|
justify-content: space-between; |
||||||
|
align-items: center; |
||||||
|
padding: 8px 0; |
||||||
|
border-bottom: 1px solid $border-secondary; |
||||||
|
|
||||||
|
&:last-child { |
||||||
|
border-bottom: none; |
||||||
|
} |
||||||
|
|
||||||
|
.label { |
||||||
|
color: $text-secondary; |
||||||
|
font-size: 12px; |
||||||
|
} |
||||||
|
|
||||||
|
.value { |
||||||
|
color: $text-primary; |
||||||
|
font-size: 14px; |
||||||
|
font-weight: 600; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 标题样式 |
||||||
|
.main-title { |
||||||
|
font-size: 24px; |
||||||
|
font-weight: 700; |
||||||
|
background: $gradient-primary; |
||||||
|
-webkit-background-clip: text; |
||||||
|
-webkit-text-fill-color: transparent; |
||||||
|
background-clip: text; |
||||||
|
text-align: center; |
||||||
|
letter-spacing: 2px; |
||||||
|
} |
||||||
|
|
||||||
|
// 状态指示器 |
||||||
|
.status-indicator { |
||||||
|
display: inline-block; |
||||||
|
width: 8px; |
||||||
|
height: 8px; |
||||||
|
border-radius: 50%; |
||||||
|
margin-right: 6px; |
||||||
|
|
||||||
|
&.online { |
||||||
|
background: $accent-green; |
||||||
|
box-shadow: 0 0 8px rgba(16, 185, 129, 0.6); |
||||||
|
} |
||||||
|
|
||||||
|
&.offline { |
||||||
|
background: $accent-red; |
||||||
|
box-shadow: 0 0 8px rgba(239, 68, 68, 0.6); |
||||||
|
} |
||||||
|
|
||||||
|
&.warning { |
||||||
|
background: $accent-orange; |
||||||
|
box-shadow: 0 0 8px rgba(245, 158, 11, 0.6); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 动画效果 |
||||||
|
@keyframes fadeInUp { |
||||||
|
from { |
||||||
|
opacity: 0; |
||||||
|
transform: translateY(20px); |
||||||
|
} |
||||||
|
to { |
||||||
|
opacity: 1; |
||||||
|
transform: translateY(0); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@keyframes pulse { |
||||||
|
0%, 100% { |
||||||
|
opacity: 1; |
||||||
|
} |
||||||
|
50% { |
||||||
|
opacity: 0.7; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.fade-in-up { |
||||||
|
animation: fadeInUp 0.6s $animation-easing; |
||||||
|
} |
||||||
|
|
||||||
|
.pulse { |
||||||
|
animation: pulse 2s infinite; |
||||||
|
} |
||||||
|
|
||||||
|
// 响应式适配 - 针对4:1宽高比优化 |
||||||
|
@media (max-width: 1919px) and (min-width: 1600px) { |
||||||
|
.dashboard-container { |
||||||
|
padding: 6px; |
||||||
|
gap: 6px; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@media (max-width: 1599px) { |
||||||
|
.dashboard-container { |
||||||
|
padding: 5px; |
||||||
|
gap: 5px; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,26 @@ |
|||||||
|
// 深色科技风格颜色变量 |
||||||
|
$bg-primary: #0a0e1a; |
||||||
|
$bg-secondary: #1a1f2e; |
||||||
|
$bg-card: rgba(26, 31, 46, 0.8); |
||||||
|
$bg-glass: rgba(255, 255, 255, 0.05); |
||||||
|
|
||||||
|
$text-primary: #ffffff; |
||||||
|
$text-secondary: #a0a8b8; |
||||||
|
$text-muted: #6b7280; |
||||||
|
|
||||||
|
$border-primary: rgba(64, 158, 255, 0.3); |
||||||
|
$border-secondary: rgba(255, 255, 255, 0.1); |
||||||
|
|
||||||
|
$accent-blue: #409eff; |
||||||
|
$accent-cyan: #00d4aa; |
||||||
|
$accent-purple: #8b5cf6; |
||||||
|
$accent-orange: #f59e0b; |
||||||
|
$accent-red: #ef4444; |
||||||
|
$accent-green: #10b981; |
||||||
|
|
||||||
|
$gradient-primary: linear-gradient(135deg, rgba(64, 158, 255, 0.2) 0%, rgba(139, 92, 246, 0.2) 100%); |
||||||
|
$gradient-secondary: linear-gradient(135deg, rgba(0, 212, 170, 0.2) 0%, rgba(16, 185, 129, 0.2) 100%); |
||||||
|
|
||||||
|
// 动画变量 |
||||||
|
$animation-duration: 0.3s; |
||||||
|
$animation-easing: cubic-bezier(0.4, 0, 0.2, 1); |
||||||
@ -0,0 +1,59 @@ |
|||||||
|
// 配置管理工具
|
||||||
|
export class ConfigManager { |
||||||
|
constructor() { |
||||||
|
this.config = null; |
||||||
|
} |
||||||
|
|
||||||
|
// 异步加载配置文件
|
||||||
|
async loadConfig() { |
||||||
|
try { |
||||||
|
const response = await fetch('/config.json'); |
||||||
|
if (!response.ok) { |
||||||
|
throw new Error('Failed to load config'); |
||||||
|
} |
||||||
|
this.config = await response.json(); |
||||||
|
return this.config; |
||||||
|
} catch (error) { |
||||||
|
console.error('Error loading config:', error); |
||||||
|
// 返回默认配置
|
||||||
|
return this.getDefaultConfig(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 获取默认配置
|
||||||
|
getDefaultConfig() { |
||||||
|
return { |
||||||
|
screen: { |
||||||
|
width: 5120, |
||||||
|
height: 1440, |
||||||
|
title: "智慧活畜交易大数据中心" |
||||||
|
}, |
||||||
|
dashboard: { |
||||||
|
refreshInterval: 5000, |
||||||
|
animationDuration: 300 |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
// 获取屏幕配置
|
||||||
|
getScreenConfig() { |
||||||
|
return this.config?.screen || this.getDefaultConfig().screen; |
||||||
|
} |
||||||
|
|
||||||
|
// 获取仪表板配置
|
||||||
|
getDashboardConfig() { |
||||||
|
return this.config?.dashboard || this.getDefaultConfig().dashboard; |
||||||
|
} |
||||||
|
|
||||||
|
// 设置CSS变量
|
||||||
|
setCSSVariables() { |
||||||
|
const screenConfig = this.getScreenConfig(); |
||||||
|
const root = document.documentElement; |
||||||
|
|
||||||
|
root.style.setProperty('--screen-width', `${screenConfig.width}px`); |
||||||
|
root.style.setProperty('--screen-height', `${screenConfig.height}px`); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 创建全局配置管理器实例
|
||||||
|
export const configManager = new ConfigManager();
|
||||||
@ -0,0 +1,164 @@ |
|||||||
|
/** |
||||||
|
* 数据管理器 - 用于加载和管理JSON配置数据 |
||||||
|
*/ |
||||||
|
class DataManager { |
||||||
|
constructor() { |
||||||
|
this.data = null |
||||||
|
this.loadPromise = null |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 加载数据配置文件 |
||||||
|
*/ |
||||||
|
async loadData() { |
||||||
|
if (this.loadPromise) { |
||||||
|
return this.loadPromise |
||||||
|
} |
||||||
|
|
||||||
|
this.loadPromise = this._fetchData() |
||||||
|
return this.loadPromise |
||||||
|
} |
||||||
|
|
||||||
|
async _fetchData() { |
||||||
|
try { |
||||||
|
console.log('正在加载数据配置文件: /data-config.json') |
||||||
|
const response = await fetch('/data-config.json') |
||||||
|
console.log('响应状态:', response.status, response.statusText) |
||||||
|
|
||||||
|
if (!response.ok) { |
||||||
|
throw new Error(`HTTP error! status: ${response.status} ${response.statusText}`) |
||||||
|
} |
||||||
|
|
||||||
|
const text = await response.text() |
||||||
|
console.log('响应内容长度:', text.length) |
||||||
|
|
||||||
|
this.data = JSON.parse(text) |
||||||
|
console.log('数据配置加载成功:', this.data) |
||||||
|
console.log('realTimeStats数据:', this.data.realTimeStats) |
||||||
|
return this.data |
||||||
|
} catch (error) { |
||||||
|
console.error('加载数据配置失败:', error) |
||||||
|
console.error('错误详情:', error.message) |
||||||
|
console.log('使用默认数据') |
||||||
|
// 返回默认数据
|
||||||
|
this.data = this.getDefaultData() |
||||||
|
return this.data |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 获取指定模块的数据 |
||||||
|
*/ |
||||||
|
getData(moduleName) { |
||||||
|
if (!this.data) { |
||||||
|
console.warn('数据尚未加载,请先调用 loadData()') |
||||||
|
return null |
||||||
|
} |
||||||
|
return this.data[moduleName] || null |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 获取所有数据 |
||||||
|
*/ |
||||||
|
getAllData() { |
||||||
|
return this.data |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 重新加载数据(用于热更新) |
||||||
|
*/ |
||||||
|
async reloadData() { |
||||||
|
this.loadPromise = null |
||||||
|
this.data = null |
||||||
|
return await this.loadData() |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 默认数据配置(当加载失败时使用) |
||||||
|
*/ |
||||||
|
getDefaultData() { |
||||||
|
return { |
||||||
|
realTimeStats: { |
||||||
|
yakTotalVolume: 0, |
||||||
|
orderTotalVolume: 0, |
||||||
|
sellerCount: 0, |
||||||
|
buyerCount: 0, |
||||||
|
systemStatus: "系统异常" |
||||||
|
}, |
||||||
|
yakTradingData: { |
||||||
|
totalCount: 0, |
||||||
|
avgPrice: 0, |
||||||
|
monthlyGrowth: 0, |
||||||
|
topRegion: "暂无数据" |
||||||
|
}, |
||||||
|
countySalesStats: [], |
||||||
|
yakPriceTrend: { |
||||||
|
months: [], |
||||||
|
prices: [], |
||||||
|
volumes: [] |
||||||
|
}, |
||||||
|
chinaMapData: [], |
||||||
|
exchangeMonitor: { |
||||||
|
onlineUsers: 0, |
||||||
|
activeTransactions: 0, |
||||||
|
systemLoad: 0, |
||||||
|
networkStatus: "未知" |
||||||
|
}, |
||||||
|
yakSalesTypeStats: [], |
||||||
|
purchaserAnalysis: [], |
||||||
|
marketEnvironment: { |
||||||
|
temperature: "--", |
||||||
|
humidity: "--", |
||||||
|
airQuality: "--", |
||||||
|
weather: "--" |
||||||
|
}, |
||||||
|
marketRealtime: { |
||||||
|
currentPrice: 0, |
||||||
|
priceChange: "0%", |
||||||
|
volume: 0, |
||||||
|
volumeChange: "0%", |
||||||
|
lastUpdate: "--" |
||||||
|
}, |
||||||
|
supplyDemandData: { |
||||||
|
supply: { total: 0, available: 0, reserved: 0 }, |
||||||
|
demand: { total: 0, pending: 0, matched: 0 }, |
||||||
|
ratio: 0 |
||||||
|
}, |
||||||
|
transactionDetails: [], |
||||||
|
announcements: ["暂无公告"] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 格式化数字显示 |
||||||
|
*/ |
||||||
|
formatNumber(num, decimals = 0) { |
||||||
|
if (typeof num !== 'number') return num |
||||||
|
return num.toLocaleString('zh-CN', { |
||||||
|
minimumFractionDigits: decimals, |
||||||
|
maximumFractionDigits: decimals |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 格式化百分比 |
||||||
|
*/ |
||||||
|
formatPercentage(num, decimals = 1) { |
||||||
|
if (typeof num !== 'number') return num |
||||||
|
return `${num.toFixed(decimals)}%` |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 格式化金额 |
||||||
|
*/ |
||||||
|
formatCurrency(amount, unit = '万元') { |
||||||
|
if (typeof amount !== 'number') return amount |
||||||
|
return `${this.formatNumber(amount, 1)}${unit}` |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 创建单例实例
|
||||||
|
export const dataManager = new DataManager() |
||||||
|
|
||||||
|
// 导出类供其他地方使用
|
||||||
|
export default DataManager
|
||||||
@ -0,0 +1 @@ |
|||||||
|
|
||||||
@ -0,0 +1,93 @@ |
|||||||
|
/** |
||||||
|
* 数据更新工具 - 用于在浏览器中手动更新数据 |
||||||
|
*/ |
||||||
|
import { dataManager } from './dataManager.js' |
||||||
|
|
||||||
|
class DataUpdater { |
||||||
|
constructor() { |
||||||
|
// 将工具挂载到全局对象,方便在控制台使用
|
||||||
|
if (typeof window !== 'undefined') { |
||||||
|
window.dataUpdater = this |
||||||
|
window.dataManager = dataManager |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 重新加载所有数据 |
||||||
|
*/ |
||||||
|
async reloadAllData() { |
||||||
|
try { |
||||||
|
console.log('正在重新加载数据...') |
||||||
|
await dataManager.reloadData() |
||||||
|
console.log('数据重新加载完成') |
||||||
|
|
||||||
|
// 触发页面刷新事件
|
||||||
|
window.dispatchEvent(new CustomEvent('dataReloaded')) |
||||||
|
|
||||||
|
return true |
||||||
|
} catch (error) { |
||||||
|
console.error('重新加载数据失败:', error) |
||||||
|
return false |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 获取当前数据 |
||||||
|
*/ |
||||||
|
getCurrentData() { |
||||||
|
return dataManager.getAllData() |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 获取指定模块数据 |
||||||
|
*/ |
||||||
|
getModuleData(moduleName) { |
||||||
|
return dataManager.getData(moduleName) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 显示所有可用的数据模块 |
||||||
|
*/ |
||||||
|
showAvailableModules() { |
||||||
|
const data = this.getCurrentData() |
||||||
|
if (data) { |
||||||
|
console.log('可用的数据模块:') |
||||||
|
Object.keys(data).forEach(key => { |
||||||
|
console.log(`- ${key}:`, typeof data[key]) |
||||||
|
}) |
||||||
|
} else { |
||||||
|
console.log('暂无数据') |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 显示使用帮助 |
||||||
|
*/ |
||||||
|
help() { |
||||||
|
console.log(` |
||||||
|
数据更新工具使用说明: |
||||||
|
|
||||||
|
1. 重新加载数据: |
||||||
|
dataUpdater.reloadAllData() |
||||||
|
|
||||||
|
2. 查看当前数据: |
||||||
|
dataUpdater.getCurrentData() |
||||||
|
|
||||||
|
3. 查看指定模块数据: |
||||||
|
dataUpdater.getModuleData('realTimeStats') |
||||||
|
|
||||||
|
4. 显示所有模块: |
||||||
|
dataUpdater.showAvailableModules() |
||||||
|
|
||||||
|
5. 显示帮助: |
||||||
|
dataUpdater.help() |
||||||
|
|
||||||
|
注意: 修改 data-config.json 文件后,使用 reloadAllData() 重新加载数据 |
||||||
|
`)
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 创建实例
|
||||||
|
export const dataUpdater = new DataUpdater() |
||||||
|
|
||||||
|
export default DataUpdater
|
||||||
@ -0,0 +1,23 @@ |
|||||||
|
import { defineConfig } from 'vite' |
||||||
|
import vue from '@vitejs/plugin-vue' |
||||||
|
|
||||||
|
export default defineConfig({ |
||||||
|
plugins: [vue()], |
||||||
|
server: { |
||||||
|
port: 3000, |
||||||
|
open: true |
||||||
|
}, |
||||||
|
css: { |
||||||
|
preprocessorOptions: { |
||||||
|
scss: { |
||||||
|
api: 'modern-compiler', |
||||||
|
additionalData: `@use "@/styles/variables.scss" as *;` |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
resolve: { |
||||||
|
alias: { |
||||||
|
'@': '/src' |
||||||
|
} |
||||||
|
} |
||||||
|
})
|
||||||
Loading…
Reference in new issue