TotalCadmiumPrediction.vue 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. <template>
  2. <div class="container">
  3. <!-- 顶部操作栏 -->
  4. <div class="toolbar">
  5. <el-button class="custom-button" :loading="isCalculating" @click="calculate">计算</el-button>
  6. <el-button class="custom-button" :disabled="isCalculating || !mapBlob" @click="exportMap">导出地图</el-button>
  7. <el-button class="custom-button" :disabled="isCalculating || !histogramBlob" @click="exportHistogram">导出直方图</el-button>
  8. <el-button class="custom-button" :disabled="isCalculating || !tableData.length" @click="exportData">导出数据</el-button>
  9. </div>
  10. <!-- 主体内容区,计算后显示 -->
  11. <div v-if="result" class="content-area">
  12. <!-- 地图区域 - 现在包含两个图片展示区 -->
  13. <div class="map-area">
  14. <div class="map-container">
  15. <!-- 地图展示 -->
  16. <div class="map-section">
  17. <h3>有效态Cd预测地图</h3>
  18. <img v-if="mapImageUrl" :src="mapImageUrl" alt="有效态Cd预测地图" class="map-image">
  19. <div v-if="loadingMap" class="loading-container">
  20. <el-icon class="loading-icon"><Loading /></el-icon>
  21. <span>地图加载中...</span>
  22. </div>
  23. </div>
  24. <!-- 直方图展示 -->
  25. <div class="histogram-section">
  26. <h3>有效态Cd预测直方图</h3>
  27. <img v-if="histogramImageUrl" :src="histogramImageUrl" alt="有效态Cd预测直方图" class="histogram-image">
  28. <div v-if="loadingMap" class="loading-container">
  29. <el-icon class="loading-icon"><Loading /></el-icon>
  30. <span>直方图加载中...</span>
  31. </div>
  32. </div>
  33. </div>
  34. </div>
  35. <!-- 表格区域 -->
  36. <div class="table-area">
  37. <h3>表格数据</h3>
  38. <el-table :data="tableData" style="width: 100%;">
  39. <el-table-column prop="name" label="名称" width="180" />
  40. <el-table-column prop="value" label="值" width="100" />
  41. <el-table-column prop="unit" label="单位" width="100" />
  42. <el-table-column prop="description" label="描述" />
  43. </el-table>
  44. </div>
  45. </div>
  46. </div>
  47. </template>
  48. <script>
  49. import * as XLSX from 'xlsx';
  50. import { saveAs } from 'file-saver';
  51. import axios from 'axios';
  52. import { Loading } from '@element-plus/icons-vue';
  53. export default {
  54. name: 'EffectiveCadmiumPrediction',
  55. components: { Loading },
  56. data() {
  57. return {
  58. isCalculating: false,
  59. loadingMap: false,
  60. loadingHistogram: false,
  61. result: false,
  62. tableData: [],
  63. mapImageUrl: null, // 存储地图图片 URL
  64. histogramImageUrl: null, // 存储直方图图片 URL
  65. mapBlob: null, // 存储地图原始Blob数据
  66. histogramBlob: null // 存储直方图原始Blob数据
  67. };
  68. },
  69. methods: {
  70. async calculate() {
  71. try {
  72. // 重置状态
  73. this.isCalculating = true;
  74. this.loadingMap = true;
  75. this.loadingHistogram = true;
  76. this.result = false;
  77. this.mapImageUrl = null;
  78. this.histogramImageUrl = null;
  79. this.mapBlob = null;
  80. this.histogramBlob = null;
  81. this.tableData = [];
  82. // 调用有效态Cd地图接口
  83. const mapResponse = await axios.post('https://soilgd.com:8000/api/cd-prediction/effective-cd/generate-and-get-map', {}, {
  84. responseType: 'blob'
  85. });
  86. // 调用有效态Cd直方图接口
  87. const histogramResponse = await axios.post('https://soilgd.com:8000/api/cd-prediction/effective-cd/generate-and-get-histogram', {}, {
  88. responseType: 'blob'
  89. });
  90. // 保存原始Blob数据用于导出
  91. this.mapBlob = mapResponse.data;
  92. this.histogramBlob = histogramResponse.data;
  93. // 为图片数据创建 URL
  94. this.mapImageUrl = URL.createObjectURL(this.mapBlob);
  95. this.histogramImageUrl = URL.createObjectURL(this.histogramBlob);
  96. // 更新表格数据
  97. this.result = true;
  98. this.tableData = [
  99. { name: '样本1', value: 10, unit: 'mg/L', description: '描述1' },
  100. { name: '样本2', value: 20, unit: 'mg/L', description: '描述2' }
  101. ];
  102. } catch (error) {
  103. console.error('获取地图或直方图失败:', error);
  104. this.$message.error('获取预测结果失败,请重试');
  105. } finally {
  106. // 无论成功失败都重置加载状态
  107. this.isCalculating = false;
  108. this.loadingMap = false;
  109. this.loadingHistogram = false;
  110. }
  111. },
  112. // 导出地图方法
  113. exportMap() {
  114. if (!this.mapBlob) {
  115. this.$message.warning('请先计算生成地图');
  116. return;
  117. }
  118. // 创建下载链接并添加到文档中
  119. const link = document.createElement('a');
  120. link.href = URL.createObjectURL(this.mapBlob);
  121. link.download = '有效态Cd预测地图.jpg';
  122. link.style.display = 'none';
  123. document.body.appendChild(link);
  124. // 触发点击事件
  125. link.click();
  126. // 清理临时URL和链接元素
  127. setTimeout(() => {
  128. document.body.removeChild(link);
  129. URL.revokeObjectURL(link.href);
  130. }, 100);
  131. },
  132. // 导出直方图方法 - 修复:确保链接元素被添加到文档中
  133. exportHistogram() {
  134. if (!this.histogramBlob) {
  135. this.$message.warning('请先计算生成直方图');
  136. return;
  137. }
  138. // 创建下载链接并添加到文档中
  139. const link = document.createElement('a');
  140. link.href = URL.createObjectURL(this.histogramBlob);
  141. link.download = '有效态Cd预测直方图.jpg';
  142. link.style.display = 'none';
  143. document.body.appendChild(link);
  144. // 触发点击事件
  145. link.click();
  146. // 清理临时URL和链接元素
  147. setTimeout(() => {
  148. document.body.removeChild(link);
  149. URL.revokeObjectURL(link.href);
  150. }, 100);
  151. },
  152. // 导出数据方法
  153. exportData() {
  154. // 创建工作簿
  155. const workbook = XLSX.utils.book_new();
  156. // 创建数据表
  157. const worksheet = XLSX.utils.json_to_sheet(this.tableData);
  158. // 将工作表添加到工作簿
  159. XLSX.utils.book_append_sheet(workbook, worksheet, '有效态Cd数据');
  160. // 生成Excel文件
  161. const excelBuffer = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' });
  162. const excelData = new Blob([excelBuffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
  163. // 保存文件
  164. saveAs(excelData, 'effective-cd-data.xlsx');
  165. }
  166. },
  167. beforeDestroy() {
  168. // 组件销毁时释放图片 URL 防止内存泄漏
  169. if (this.mapImageUrl) URL.revokeObjectURL(this.mapImageUrl);
  170. if (this.histogramImageUrl) URL.revokeObjectURL(this.histogramImageUrl);
  171. }
  172. };
  173. </script>
  174. <style scoped>
  175. /* 样式保持不变 */
  176. .container {
  177. padding: 20px;
  178. background-color: #f5f7fa;
  179. min-height: 100vh;
  180. box-sizing: border-box;
  181. }
  182. .toolbar {
  183. display: flex;
  184. align-items: center;
  185. gap: 10px;
  186. margin-bottom: 20px;
  187. }
  188. .custom-button {
  189. background-color: #47C3B9 !important;
  190. color: #DCFFFA !important;
  191. border: none;
  192. border-radius: 155px;
  193. padding: 10px 20px;
  194. font-weight: bold;
  195. }
  196. .content-area {
  197. display: flex;
  198. gap: 20px;
  199. }
  200. .map-area {
  201. flex: 1;
  202. min-width: 300px;
  203. }
  204. .map-container {
  205. display: flex;
  206. flex-direction: column;
  207. gap: 20px;
  208. }
  209. .map-section, .histogram-section {
  210. background-color: white;
  211. border-radius: 8px;
  212. padding: 15px;
  213. box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
  214. position: relative;
  215. }
  216. .map-image, .histogram-image {
  217. width: 100%;
  218. max-height: 600px;
  219. object-fit: contain;
  220. border-radius: 4px;
  221. }
  222. .map-placeholder {
  223. background-color: #cce5ff;
  224. height: 200px;
  225. border-radius: 8px;
  226. display: flex;
  227. align-items: center;
  228. justify-content: center;
  229. font-weight: bold;
  230. font-size: 16px;
  231. color: #003366;
  232. }
  233. .table-area {
  234. flex: 1;
  235. background-color: white;
  236. border-radius: 8px;
  237. padding: 15px;
  238. box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
  239. }
  240. .loading-container {
  241. display: flex;
  242. flex-direction: column;
  243. align-items: center;
  244. justify-content: center;
  245. height: 200px;
  246. color: #47C3B9;
  247. }
  248. .loading-icon {
  249. font-size: 36px;
  250. margin-bottom: 10px;
  251. animation: rotate 2s linear infinite;
  252. }
  253. @keyframes rotate {
  254. from {
  255. transform: rotate(0deg);
  256. }
  257. to {
  258. transform: rotate(360deg);
  259. }
  260. }
  261. </style>