strawRemovalInputFlux.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. <template>
  2. <div class="agricultural-input-management">
  3. <!-- 直接展示结果页面 -->
  4. <div class="page-container">
  5. <el-card class="results-card">
  6. <div class="results-content">
  7. <!-- 地图区域 -->
  8. <div class="map-section">
  9. <h3>秸秆移除Cd通量分布图</h3>
  10. <div v-if="loading" class="loading-container">
  11. <el-icon class="loading-icon"><Loading /></el-icon>
  12. <span>数据加载中...</span>
  13. </div>
  14. <div v-else-if="error" class="error-container">
  15. <el-icon class="error-icon"><Warning /></el-icon>
  16. <span>{{ error }}</span>
  17. </div>
  18. <div v-else class="image-container">
  19. <img :src="visualizationImage" class="result-image"></img>
  20. </div>
  21. </div>
  22. <!-- 统计图表区域 -->
  23. <div class="stats-area">
  24. <h3>秸秆移除Cd通量统计信息</h3>
  25. <div class="model-info">
  26. <el-tag type="info">秸秆移除Cd通量模型</el-tag>
  27. <span class="update-time">
  28. 最后更新: {{ reportTime }}
  29. </span>
  30. </div>
  31. <div v-if="loading" class="loading-container">
  32. <el-icon class="loading-icon"><Loading /></el-icon>
  33. <span>统计数据加载中...</span>
  34. </div>
  35. <div v-else-if="error" class="error-container">
  36. <el-icon class="error-icon"><Warning /></el-icon>
  37. <span>{{ error }}</span>
  38. </div>
  39. <div v-else class="stats-container">
  40. <div class="stats-grid">
  41. <div class="stat-card">
  42. <div class="stat-value">{{ statistics.total_samples.toLocaleString() }}</div>
  43. <div class="stat-label">总样本数</div>
  44. </div>
  45. <div class="stat-card">
  46. <div class="stat-value">{{ statistics.mean_flux.toFixed(4) }}</div>
  47. <div class="stat-label">平均通量</div>
  48. </div>
  49. <div class="stat-card">
  50. <div class="stat-value">{{ statistics.max_flux.toFixed(4) }}</div>
  51. <div class="stat-label">最大通量</div>
  52. </div>
  53. <div class="stat-card">
  54. <div class="stat-value">{{ statistics.min_flux.toFixed(4) }}</div>
  55. <div class="stat-label">最小通量</div>
  56. </div>
  57. </div>
  58. </div>
  59. </div>
  60. </div>
  61. </el-card>
  62. </div>
  63. </div>
  64. </template>
  65. <script>
  66. import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
  67. import {
  68. ElButton,
  69. ElCard,
  70. ElTable,
  71. ElTableColumn,
  72. ElTag,
  73. ElIcon,
  74. ElPagination
  75. } from 'element-plus';
  76. import {
  77. Loading,
  78. Picture,
  79. Warning
  80. } from '@element-plus/icons-vue';
  81. import { api8000 } from '@/utils/request';
  82. export default {
  83. components: {
  84. ElButton,
  85. ElCard,
  86. ElTable,
  87. ElTableColumn,
  88. ElTag,
  89. ElIcon,
  90. ElPagination,
  91. Loading,
  92. Picture,
  93. Warning
  94. },
  95. props: {
  96. area: {
  97. type: String,
  98. default: '乐昌市'
  99. }
  100. },
  101. setup(props) {
  102. // 响应式数据
  103. const results = ref([]);
  104. const statistics = ref({
  105. total_samples: 0,
  106. mean_flux: 0,
  107. max_flux: 0,
  108. min_flux: 0
  109. });
  110. const formula = ref("");
  111. const unit = ref("");
  112. const visualizationImage = ref("");
  113. const reportTime = ref("");
  114. const loading = ref(true);
  115. const error = ref(null);
  116. // 分页相关变量
  117. const currentPage = ref(1);
  118. const pageSize = ref(5);
  119. // 计算分页后的数据
  120. const tableData = computed(() => {
  121. const start = (currentPage.value - 1) * pageSize.value;
  122. const end = start + pageSize.value;
  123. return results.value.slice(start, end);
  124. });
  125. // 分页事件处理
  126. const handleSizeChange = (newSize) => {
  127. pageSize.value = newSize;
  128. currentPage.value = 1; // 重置到第一页
  129. };
  130. const handleCurrentChange = (newPage) => {
  131. currentPage.value = newPage;
  132. };
  133. // 从API获取数据
  134. const fetchData = async () => {
  135. try {
  136. loading.value = true;
  137. error.value = null;
  138. // 获取计算结果数据
  139. const dataResponse = await api8000.get(
  140. `/api/cd-flux-removal/straw-removal?area=${encodeURIComponent(props.area)}`
  141. );
  142. if (!dataResponse.data.success) {
  143. throw new Error(`API错误: ${dataResponse.data.message}`);
  144. }
  145. // 设置数据
  146. results.value = dataResponse.data.data.results;
  147. statistics.value = dataResponse.data.data.statistics;
  148. formula.value = dataResponse.data.data.formula;
  149. unit.value = dataResponse.data.data.unit;
  150. // 获取可视化图片
  151. try {
  152. const imageResponse = await api8000.get(
  153. `/api/cd-flux-removal/straw-removal/latest-map/${encodeURIComponent(props.area)}`,
  154. { responseType: 'blob' }
  155. );
  156. // 创建图片Blob URL
  157. const blob = new Blob([imageResponse.data], { type: imageResponse.headers['content-type'] });
  158. visualizationImage.value = URL.createObjectURL(blob);
  159. } catch (imgErr) {
  160. console.error('图片加载错误:', imgErr);
  161. }
  162. // 设置报告时间
  163. reportTime.value = new Date().toLocaleString('zh-CN', {
  164. year: 'numeric',
  165. month: 'long',
  166. day: 'numeric',
  167. hour: '2-digit',
  168. minute: '2-digit'
  169. });
  170. } catch (err) {
  171. console.error('数据获取错误:', err);
  172. error.value = err.message || '获取数据时发生错误';
  173. } finally {
  174. loading.value = false;
  175. }
  176. };
  177. // 组件挂载时获取数据
  178. onMounted(() => {
  179. fetchData();
  180. });
  181. onBeforeUnmount(() => {
  182. if (visualizationImage.value) {
  183. URL.revokeObjectURL(visualizationImage.value);
  184. }
  185. });
  186. return {
  187. results,
  188. statistics,
  189. formula,
  190. unit,
  191. visualizationImage,
  192. reportTime,
  193. loading,
  194. error,
  195. tableData,
  196. currentPage,
  197. pageSize,
  198. handleSizeChange,
  199. handleCurrentChange
  200. };
  201. }
  202. };
  203. </script>
  204. <style scoped>
  205. /* 整体布局优化 - 与农业投入品保持一致 */
  206. .agricultural-input-management {
  207. padding: 20px;
  208. background: linear-gradient(
  209. 135deg,
  210. rgba(230, 247, 255, 0.7) 0%,
  211. rgba(240, 248, 255, 0.7) 100%
  212. );
  213. min-height: 100vh;
  214. box-sizing: border-box;
  215. }
  216. .page-container {
  217. width: 100%;
  218. height: 100%;
  219. }
  220. /* 工具栏样式 */
  221. .toolbar {
  222. display: flex;
  223. justify-content: flex-start;
  224. gap: 15px;
  225. margin-bottom: 20px;
  226. padding: 15px;
  227. background-color: rgba(255, 255, 255, 0.8);
  228. border-radius: 8px;
  229. box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
  230. backdrop-filter: blur(5px);
  231. }
  232. .custom-button {
  233. background-color: #47C3B9 !important;
  234. color: #DCFFFA !important;
  235. border: none;
  236. border-radius: 155px;
  237. padding: 10px 20px;
  238. font-weight: bold;
  239. display: flex;
  240. align-items: center;
  241. }
  242. /* 结果卡片样式 */
  243. .results-card {
  244. background-color: rgba(255, 255, 255, 0.8);
  245. border-radius: 8px;
  246. padding: 20px;
  247. box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
  248. backdrop-filter: blur(5px);
  249. height: 100%;
  250. box-sizing: border-box;
  251. }
  252. .results-content {
  253. height: 100%;
  254. display: flex;
  255. flex-direction: column;
  256. }
  257. .map-section, .stats-area {
  258. background-color: rgba(255, 255, 255, 0.8);
  259. border-radius: 8px;
  260. padding: 20px;
  261. margin-bottom: 20px;
  262. box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
  263. }
  264. h3 {
  265. margin-bottom: 15px;
  266. color: #333;
  267. font-size: 18px;
  268. font-weight: 600;
  269. }
  270. .model-info {
  271. display: flex;
  272. align-items: center;
  273. gap: 15px;
  274. margin-bottom: 15px;
  275. }
  276. .update-time {
  277. color: #666;
  278. font-size: 14px;
  279. }
  280. /* 统计卡片样式 */
  281. .stats-grid {
  282. display: grid;
  283. grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  284. gap: 20px;
  285. margin-bottom: 20px;
  286. }
  287. .stat-card {
  288. background: rgba(178, 235, 242, 0.3);
  289. border-radius: 15px;
  290. padding: 20px;
  291. text-align: center;
  292. box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);
  293. transition: all 0.3s ease;
  294. }
  295. .stat-card:hover {
  296. transform: translateY(-5px);
  297. background: rgba(178, 235, 242, 0.5);
  298. }
  299. .stat-value {
  300. font-size: 1.8rem;
  301. font-weight: 700;
  302. color: #006064;
  303. margin-bottom: 8px;
  304. }
  305. .stat-label {
  306. font-size: 1.1rem;
  307. color: #00838f;
  308. }
  309. /* 图片容器限定大小 */
  310. .image-container {
  311. width: 100%;
  312. height: 500px;
  313. display: flex;
  314. justify-content: center;
  315. align-items: center;
  316. background-color: #f9f9f9;
  317. border-radius: 8px;
  318. overflow: hidden;
  319. }
  320. .result-image {
  321. max-width: 100%;
  322. max-height: 100%;
  323. object-fit: contain;
  324. }
  325. .loading-container, .error-container {
  326. display: flex;
  327. flex-direction: column;
  328. align-items: center;
  329. justify-content: center;
  330. height: 300px;
  331. gap: 15px;
  332. }
  333. .loading-container {
  334. color: #47C3B9;
  335. }
  336. .error-container {
  337. color: #F56C6C;
  338. }
  339. .loading-icon {
  340. font-size: 36px;
  341. margin-bottom: 10px;
  342. animation: rotate 2s linear infinite;
  343. }
  344. .error-icon {
  345. font-size: 36px;
  346. margin-bottom: 10px;
  347. }
  348. .no-data {
  349. display: flex;
  350. flex-direction: column;
  351. align-items: center;
  352. justify-content: center;
  353. height: 300px;
  354. color: #999;
  355. font-size: 16px;
  356. }
  357. .no-data .el-icon {
  358. font-size: 48px;
  359. margin-bottom: 10px;
  360. }
  361. .details-table {
  362. margin-top: 20px;
  363. }
  364. .custom-pagination {
  365. margin-top: 20px;
  366. display: flex;
  367. justify-content: center;
  368. }
  369. @keyframes rotate {
  370. from { transform: rotate(0deg); }
  371. to { transform: rotate(360deg); }
  372. }
  373. /* 响应式设计 */
  374. @media (max-width: 768px) {
  375. .toolbar {
  376. flex-direction: column;
  377. align-items: stretch;
  378. }
  379. .stats-grid {
  380. grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  381. }
  382. .image-container {
  383. height: 300px;
  384. }
  385. }
  386. @media (max-width: 480px) {
  387. .agricultural-input-management {
  388. padding: 10px;
  389. }
  390. .results-card {
  391. padding: 15px;
  392. }
  393. .stats-grid {
  394. grid-template-columns: 1fr;
  395. }
  396. .stat-card {
  397. padding: 15px;
  398. }
  399. .image-container {
  400. height: 250px;
  401. }
  402. .stat-value {
  403. font-size: 1.5rem;
  404. }
  405. }
  406. </style>