grainRemovalInputFlux.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. <template>
  2. <div class="container">
  3. <div class="gradient-card">
  4. <div class="card-header">
  5. <h1>籽粒移除输出通量计算结果</h1>
  6. <p>{{ area }}地区作物籽粒移除Cd通量分析报告</p>
  7. </div>
  8. <div v-if="loading" class="loading-section">
  9. <i class="fas fa-spinner fa-spin"></i> 正在加载数据...
  10. </div>
  11. <div v-if="error" class="error-section">
  12. <i class="fas fa-exclamation-triangle"></i> {{ error }}
  13. </div>
  14. <template v-if="!loading && !error">
  15. <div class="statistics-section">
  16. <h2><i class="fas fa-chart-bar"></i> 统计分析</h2>
  17. <div class="stats-grid">
  18. <div class="stat-card">
  19. <div class="stat-value">{{ statistics.total_samples.toLocaleString() }}</div>
  20. <div class="stat-label">总样本数</div>
  21. </div>
  22. <div class="stat-card">
  23. <div class="stat-value">{{ statistics.mean_flux.toFixed(2) }} g/ha/a</div>
  24. <div class="stat-label">平均通量</div>
  25. </div>
  26. <div class="stat-card">
  27. <div class="stat-value">{{ statistics.max_flux.toFixed(2) }} g/ha/a</div>
  28. <div class="stat-label">最大通量</div>
  29. </div>
  30. <div class="stat-card">
  31. <div class="stat-value">{{ statistics.min_flux.toFixed(2) }} g/ha/a</div>
  32. <div class="stat-label">最小通量</div>
  33. </div>
  34. </div>
  35. </div>
  36. <div class="visualization-section">
  37. <h2><i class="fas fa-map"></i> 可视化分析图</h2>
  38. <img v-if="visualizationImage" :src="visualizationImage" alt="Cd含量地图" class="result-image">
  39. <div v-if="visualizationError" class="error-section">
  40. <i class="fas fa-exclamation-triangle"></i> {{ visualizationError }}
  41. </div>
  42. <div v-else class="image-container">
  43. </div>
  44. </div>
  45. <div class="data-section">
  46. <h2><i class="fas fa-table"></i> 详细数据</h2>
  47. <el-table
  48. :data="tableData"
  49. height="400"
  50. style="width: 100%"
  51. class="custom-table"
  52. :header-cell-style="{ background: 'rgba(0, 150, 136, 0.1)', color: '#006064', fontWeight: '600' }"
  53. >
  54. <el-table-column prop="farmland_id" label="农田ID" width="120" align="center"></el-table-column>
  55. <el-table-column prop="sample_id" label="样本ID" width="120" align="center"></el-table-column>
  56. <el-table-column prop="crop_cd_value" label="作物镉Cd(mg/kg)" align="center">
  57. <template #default="scope">
  58. {{ scope.row.crop_cd_value.toFixed(5) }}
  59. </template>
  60. </el-table-column>
  61. <el-table-column prop="f11_yield" label="作物亩产量(斤)" width="150" align="center"></el-table-column>
  62. <el-table-column prop="grain_removal_flux" label="籽粒移除通量(g/ha/a)" width="170" align="center">
  63. <template #default="scope">
  64. {{ scope.row.grain_removal_flux.toFixed(5) }}
  65. </template>
  66. </el-table-column>
  67. </el-table>
  68. <el-pagination
  69. background
  70. layout="prev, pager, next, sizes, total"
  71. :total="results.length"
  72. :page-size="pageSize"
  73. :current-page="currentPage"
  74. :page-sizes="[5, 10, 20, 50]"
  75. @size-change="handleSizeChange"
  76. @current-change="handleCurrentChange"
  77. class="custom-pagination"
  78. >
  79. </el-pagination>
  80. </div>
  81. <div class="formula-section">
  82. <h2><i class="fas fa-calculator"></i> 计算公式</h2>
  83. <div class="formula-card">
  84. {{ formula }}
  85. </div>
  86. <div class="unit-note">
  87. <i class="fas fa-info-circle"></i> 单位: {{ unit }}
  88. </div>
  89. </div>
  90. <div class="card-footer">
  91. <p><i class="fas fa-clock"></i> 报告生成时间: {{ reportTime }}</p>
  92. </div>
  93. </template>
  94. </div>
  95. </div>
  96. </template>
  97. <script>
  98. import { ref, computed, onMounted } from 'vue';
  99. import { ElTable, ElTableColumn, ElPagination } from 'element-plus';
  100. export default {
  101. name: 'GrainRemovalResult',
  102. components: {
  103. ElTable,
  104. ElTableColumn,
  105. ElPagination
  106. },
  107. props: {
  108. area: {
  109. type: String,
  110. default: '韶关'
  111. }
  112. },
  113. setup(props) {
  114. // 响应式数据
  115. const results = ref([]);
  116. const statistics = ref({
  117. total_samples: 0,
  118. mean_flux: 0,
  119. max_flux: 0,
  120. min_flux: 0
  121. });
  122. const formula = ref("");
  123. const unit = ref("");
  124. const visualizationImage = ref("");
  125. const reportTime = ref("");
  126. const loading = ref(true);
  127. const error = ref(null);
  128. // 分页相关变量
  129. const currentPage = ref(1);
  130. const pageSize = ref(5);
  131. // 计算分页后的数据
  132. const tableData = computed(() => {
  133. const start = (currentPage.value - 1) * pageSize.value;
  134. const end = start + pageSize.value;
  135. return results.value.slice(start, end);
  136. });
  137. // 分页事件处理
  138. const handleSizeChange = (newSize) => {
  139. pageSize.value = newSize;
  140. currentPage.value = 1; // 重置到第一页
  141. };
  142. const handleCurrentChange = (newPage) => {
  143. currentPage.value = newPage;
  144. };
  145. // 从API获取数据
  146. const fetchData = async () => {
  147. try {
  148. // 获取计算结果数据
  149. const dataResponse = await fetch(
  150. `http://127.0.0.1:8000/api/cd-flux-removal/grain-removal?area=${encodeURIComponent(props.area)}`
  151. );
  152. if (!dataResponse.ok) {
  153. throw new Error(`数据获取失败: ${dataResponse.statusText}`);
  154. }
  155. const dataJson = await dataResponse.json();
  156. if (!dataJson.success) {
  157. throw new Error(`API错误: ${dataJson.message}`);
  158. }
  159. // 设置数据
  160. results.value = dataJson.data.results;
  161. statistics.value = dataJson.data.statistics;
  162. formula.value = dataJson.data.formula;
  163. unit.value = dataJson.data.unit;
  164. // 获取可视化图片
  165. const imageResponse = await fetch(
  166. `http://127.0.0.1:8000/api/cd-flux-removal/grain-removal/visualize?area=${encodeURIComponent(props.area)}&level=city`
  167. );
  168. if (!imageResponse.ok) {
  169. throw new Error(`图片获取失败: ${imageResponse.statusText}`);
  170. }
  171. // 创建图片Blob URL
  172. const blob = await imageResponse.blob();
  173. visualizationImage.value = URL.createObjectURL(blob);
  174. // 设置报告时间
  175. reportTime.value = new Date().toLocaleString('zh-CN', {
  176. year: 'numeric',
  177. month: 'long',
  178. day: 'numeric',
  179. hour: '2-digit',
  180. minute: '2-digit'
  181. });
  182. } catch (err) {
  183. console.error('数据获取错误:', err);
  184. error.value = err.message || '获取数据时发生错误';
  185. } finally {
  186. loading.value = false;
  187. }
  188. };
  189. // 组件挂载时获取数据
  190. onMounted(() => {
  191. fetchData();
  192. });
  193. return {
  194. results,
  195. statistics,
  196. formula,
  197. unit,
  198. visualizationImage,
  199. reportTime,
  200. loading,
  201. error,
  202. tableData,
  203. currentPage,
  204. pageSize,
  205. handleSizeChange,
  206. handleCurrentChange
  207. };
  208. }
  209. };
  210. </script>
  211. <style scoped>
  212. /* 保留您原有的样式,只添加Element UI的自定义样式 */
  213. .container {
  214. display: flex;
  215. justify-content: center;
  216. align-items: center;
  217. min-height: 100vh;
  218. padding: 20px;
  219. background: linear-gradient(135deg, #e6f7ff, #b3e0ff);
  220. }
  221. .gradient-card {
  222. background: linear-gradient(135deg,
  223. rgba(250, 253, 255, 0.9),
  224. rgba(220, 240, 255, 0.9));
  225. border-radius: 20px;
  226. box-shadow: 0 15px 40px rgba(0, 0, 0, 0.15);
  227. padding: 30px;
  228. width: 100%;
  229. max-width: 1000px;
  230. backdrop-filter: blur(8px);
  231. border: none;
  232. display: flex;
  233. flex-direction: column;
  234. overflow: hidden;
  235. }
  236. .card-header {
  237. text-align: center;
  238. margin-bottom: 30px;
  239. border-bottom: 2px solid rgba(0, 96, 100, 0.1);
  240. padding-bottom: 20px;
  241. }
  242. .card-header h1 {
  243. font-size: 2.2rem;
  244. color: #006064;
  245. margin-bottom: 10px;
  246. font-weight: 700;
  247. }
  248. .card-header p {
  249. font-size: 1.2rem;
  250. color: #00838f;
  251. opacity: 0.9;
  252. }
  253. .loading-section, .error-section {
  254. text-align: center;
  255. padding: 40px;
  256. font-size: 1.4rem;
  257. color: #006064;
  258. }
  259. .loading-section i {
  260. margin-right: 15px;
  261. font-size: 1.8rem;
  262. color: #00838f;
  263. }
  264. .error-section i {
  265. margin-right: 15px;
  266. font-size: 1.8rem;
  267. color: #d32f2f;
  268. }
  269. .stats-grid {
  270. display: grid;
  271. grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  272. gap: 20px;
  273. margin-top: 20px;
  274. }
  275. .stat-card {
  276. background: rgba(178, 235, 242, 0.3);
  277. border-radius: 15px;
  278. padding: 20px;
  279. text-align: center;
  280. box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);
  281. transition: transform 0.3s ease;
  282. }
  283. .stat-card:hover {
  284. transform: translateY(-5px);
  285. background: rgba(178, 235, 242, 0.5);
  286. }
  287. .stat-value {
  288. font-size: 1.8rem;
  289. font-weight: 700;
  290. color: #006064;
  291. margin-bottom: 8px;
  292. }
  293. .stat-label {
  294. font-size: 1.1rem;
  295. color: #00838f;
  296. }
  297. .visualization-section,
  298. .data-section,
  299. .formula-section,
  300. .statistics-section {
  301. margin-bottom: 30px;
  302. padding: 20px;
  303. background: rgba(255, 255, 255, 0.6);
  304. border-radius: 15px;
  305. box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
  306. }
  307. .visualization-section h2,
  308. .data-section h2,
  309. .formula-section h2,
  310. .statistics-section h2 {
  311. font-size: 1.6rem;
  312. color: #006064;
  313. margin-bottom: 20px;
  314. display: flex;
  315. align-items: center;
  316. gap: 10px;
  317. }
  318. .result-image {
  319. max-width: 100%;
  320. max-height: 500px;
  321. border-radius: 10px;
  322. box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1);
  323. }
  324. .formula-card {
  325. font-family: 'Courier New', monospace;
  326. font-size: 1.2rem;
  327. background: rgba(255, 255, 255, 0.8);
  328. padding: 20px;
  329. border-radius: 10px;
  330. text-align: center;
  331. margin: 20px 0;
  332. border: 1px dashed #00838f;
  333. color: #006064;
  334. }
  335. .unit-note {
  336. text-align: center;
  337. font-size: 1.1rem;
  338. color: #00838f;
  339. display: flex;
  340. align-items: center;
  341. justify-content: center;
  342. gap: 8px;
  343. }
  344. .card-footer {
  345. text-align: center;
  346. font-size: 1rem;
  347. color: #00838f;
  348. padding-top: 20px;
  349. border-top: 1px solid rgba(0, 150, 136, 0.2);
  350. display: flex;
  351. align-items: center;
  352. justify-content: center;
  353. gap: 10px;
  354. }
  355. /* 响应式设计 */
  356. @media (max-width: 768px) {
  357. .stats-grid {
  358. grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  359. }
  360. .card-header h1 {
  361. font-size: 1.8rem;
  362. }
  363. .visualization-section h2,
  364. .data-section h2,
  365. .formula-section h2,
  366. .statistics-section h2 {
  367. font-size: 1.4rem;
  368. }
  369. }
  370. @media (max-width: 480px) {
  371. .gradient-card {
  372. padding: 15px;
  373. }
  374. .stats-grid {
  375. grid-template-columns: 1fr;
  376. }
  377. .stat-card {
  378. padding: 15px;
  379. }
  380. .stat-value {
  381. font-size: 1.5rem;
  382. }
  383. }
  384. .result-image {
  385. width: 100%;
  386. max-height: 400px;
  387. object-fit: contain;
  388. border-radius: 8px;
  389. border: 1px solid #e4e7ed;
  390. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  391. background-color: #f8f8f8;
  392. }
  393. /* Element UI 自定义样式 */
  394. .custom-table {
  395. border-radius: 10px;
  396. overflow: hidden;
  397. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
  398. margin-top: 20px;
  399. }
  400. .custom-table .el-table__row {
  401. transition: background-color 0.3s;
  402. }
  403. .custom-table .el-table__row:hover {
  404. background-color: rgba(178, 235, 242, 0.3) !important;
  405. }
  406. .custom-table .el-table__row:nth-child(even) {
  407. background-color: rgba(178, 235, 242, 0.15);
  408. }
  409. .custom-pagination {
  410. margin-top: 20px;
  411. display: flex;
  412. justify-content: center;
  413. }
  414. </style>