irriWaterInputFlux.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601
  1. <template>
  2. <div class="irrigation-management" style="display: flex; justify-content: center;">
  3. <!-- 计算页面 -->
  4. <div v-if="showCalculation" class="page-container">
  5. <el-card shadow="always" class="gradient-card">
  6. <!-- 将单选按钮组改为多选框组 -->
  7. <el-checkbox-group v-model="selectedLandTypes" style="width: 100%;">
  8. <!-- 水田 -->
  9. <el-row :gutter="20" style="margin-bottom: 10px; align-items: center;">
  10. <el-col :span="6">
  11. <div class="radio-container">
  12. <el-checkbox label="water" border>水田</el-checkbox>
  13. </div>
  14. </el-col>
  15. <el-col :span="9">
  16. <div class="input-title">灌溉水用量 (m³/亩/年)</div>
  17. <el-input
  18. v-model="irrigationWaterUsage"
  19. placeholder="请输入灌溉水用量"
  20. style="margin-top: 10px;"
  21. />
  22. </el-col>
  23. <el-col :span="9">
  24. <div class='input-title'>灌溉水有效利用率 (%)</div>
  25. <el-input
  26. v-model="irrigationEfficiency"
  27. placeholder="请输入灌溉水有效利用率"
  28. style="margin-top: 10px;"
  29. />
  30. </el-col>
  31. </el-row>
  32. <!-- 水浇地 -->
  33. <el-row :gutter="20" style="margin-bottom: 10px; align-items: center;">
  34. <el-col :span="6">
  35. <div class="radio-container">
  36. <el-checkbox label="irrigated" border>水浇地</el-checkbox>
  37. </div>
  38. </el-col>
  39. <el-col :span="9">
  40. <div class="input-title">灌溉水用量 (m³/亩/年)</div>
  41. <el-input
  42. v-model="irrigatedWaterUsage"
  43. placeholder="请输入灌溉水用量"
  44. style="margin-top: 10px;"
  45. />
  46. </el-col>
  47. <el-col :span="9">
  48. <div class="input-title">灌溉水有效利用率 (%)</div>
  49. <el-input
  50. v-model="irrigatedEfficiency"
  51. placeholder="请输入灌溉水有效利用率"
  52. style="margin-top: 10px;"
  53. />
  54. </el-col>
  55. </el-row>
  56. <!-- 旱地 -->
  57. <el-row :gutter="20" style="margin-bottom: 10px; align-items: center;">
  58. <el-col :span="6">
  59. <div class="radio-container">
  60. <el-checkbox label="dry" border>旱地</el-checkbox>
  61. </div>
  62. </el-col>
  63. <el-col :span="9">
  64. <div class="input-title">灌溉水用量 (m³/亩/年)</div>
  65. <el-input
  66. v-model="dryWaterUsage"
  67. placeholder="请输入灌溉水用量"
  68. style="margin-top: 10px;"
  69. />
  70. </el-col>
  71. <el-col :span="9">
  72. <div class="input-title">灌溉水有效利用率 (%)</div>
  73. <el-input
  74. v-model="dryEfficiency"
  75. placeholder="请输入灌溉水有效利用率"
  76. style="margin-top: 10px;"
  77. />
  78. </el-col>
  79. </el-row>
  80. </el-checkbox-group>
  81. <el-row justify="center" style="margin-top: 20px;">
  82. <el-button
  83. class="calculate-btn"
  84. @click="calculateFlux"
  85. :loading="loading"
  86. >
  87. 计算灌溉水输入通量
  88. </el-button>
  89. </el-row>
  90. </el-card>
  91. </div>
  92. <!-- 结果页面 -->
  93. <div v-if="showResults" class="page-container">
  94. <el-card shadow="always" class="results-card">
  95. <div class="results-header">
  96. <el-button
  97. type="primary"
  98. @click="backToCalculation"
  99. class="back-button">
  100. 返回计算
  101. </el-button>
  102. <div class="result-title">计算结果</div>
  103. </div>
  104. <div class="results-container">
  105. <!-- 图像区域(地图和直方图) -->
  106. <div class="image-row">
  107. <div class="image-container">
  108. <div class="result-subtitle">Cd含量地图</div>
  109. <div class="image-wrapper">
  110. <img v-if="mapImageUrl" :src="mapImageUrl" alt="Cd含量地图" class="result-image">
  111. <div v-else class="image-placeholder">地图加载中...</div>
  112. </div>
  113. </div>
  114. <div class="image-container">
  115. <div class="result-subtitle">Cd含量分布直方图</div>
  116. <div class="image-wrapper">
  117. <img v-if="histogramImageUrl" :src="histogramImageUrl" alt="Cd含量地图" class="result-image">
  118. <div v-else class="image-placeholder">直方图加载中...</div>
  119. </div>
  120. </div>
  121. </div>
  122. <!-- 统计结果 -->
  123. <el-row style="margin-top: 30px;">
  124. <el-col :span="24">
  125. <div class="result-subtitle">统计结果</div>
  126. <div class="statistics-container">
  127. <!-- 基础统计表格 -->
  128. <el-table
  129. v-if="statisticsData"
  130. :data="[statisticsData]"
  131. border
  132. size="small"
  133. style="width: 100%; margin-bottom: 20px;"
  134. >
  135. <el-table-column prop="土地类型" label="土地类型" align="center"></el-table-column>
  136. <el-table-column prop="数据更新时间" label="数据更新时间" align="center"></el-table-column>
  137. <el-table-column prop="数据点总数" label="数据点总数" align="center"></el-table-column>
  138. <el-table-column prop="均值" label="均值" align="center" :formatter="formatNumber"></el-table-column>
  139. <el-table-column prop="中位数" label="中位数" align="center" :formatter="formatNumber"></el-table-column>
  140. <el-table-column prop="标准差" label="标准差" align="center" :formatter="formatNumber"></el-table-column>
  141. <el-table-column prop="最小值" label="最小值" align="center" :formatter="formatNumber"></el-table-column>
  142. <el-table-column prop="最大值" label="最大值" align="center" :formatter="formatNumber"></el-table-column>
  143. </el-table>
  144. <!-- 分位数和形态统计表格 -->
  145. <el-table
  146. v-if="statisticsData"
  147. :data="[statisticsData]"
  148. border
  149. size="small"
  150. style="width: 100%; margin-bottom: 20px;"
  151. >
  152. <el-table-column prop="25%分位数" label="25%分位数" align="center" :formatter="formatNumber"></el-table-column>
  153. <el-table-column prop="75%分位数" label="75%分位数" align="center" :formatter="formatNumber"></el-table-column>
  154. <el-table-column prop="偏度" label="偏度" align="center" :formatter="formatNumber"></el-table-column>
  155. <el-table-column prop="峰度" label="峰度" align="center" :formatter="formatNumber"></el-table-column>
  156. </el-table>
  157. </div>
  158. </el-col>
  159. </el-row>
  160. </div>
  161. </el-card>
  162. </div>
  163. </div>
  164. </template>
  165. <script>
  166. import { ref } from 'vue';
  167. import axios from 'axios';
  168. import {
  169. ElCheckbox,
  170. ElCheckboxGroup,
  171. ElInput,
  172. ElButton,
  173. ElMessage,
  174. ElCard,
  175. ElRow,
  176. ElCol,
  177. ElTable,
  178. ElTableColumn
  179. } from 'element-plus';
  180. // 土地类型映射
  181. const landTypeMap = {
  182. water: '水田',
  183. irrigated: '水浇地',
  184. dry: '旱地'
  185. };
  186. export default {
  187. components: {
  188. ElCheckbox,
  189. ElCheckboxGroup,
  190. ElInput,
  191. ElButton,
  192. ElMessage,
  193. ElCard,
  194. ElRow,
  195. ElCol,
  196. ElTable,
  197. ElTableColumn
  198. },
  199. setup() {
  200. // 将单选改为多选,初始值为空数组
  201. const selectedLandTypes = ref([]);
  202. // 设置默认值
  203. const irrigationWaterUsage = ref('711');
  204. const irrigationEfficiency = ref('0.524');
  205. const irrigatedWaterUsage = ref('427');
  206. const irrigatedEfficiency = ref('0.599');
  207. const dryWaterUsage = ref('200');
  208. const dryEfficiency = ref('0.7');
  209. const fluxResult = ref(null);
  210. const showCalculation = ref(true); // 显示计算页面
  211. const showResults = ref(false); // 显示结果页面
  212. const loading = ref(false);
  213. // 结果展示数据
  214. const mapImageUrl = ref('');
  215. const histogramImageUrl = ref('');
  216. const statisticsData = ref(null);
  217. // 格式化数字显示(保留4位小数)
  218. const formatNumber = (row, column, cellValue) => {
  219. if (typeof cellValue === 'number') {
  220. return cellValue.toFixed(4);
  221. }
  222. return cellValue;
  223. };
  224. // 获取默认地图
  225. const fetchDefaultMap = async (landTypeChinese) => {
  226. try {
  227. const response = await axios.get('http://localhost:8000/api/water/default-map', {
  228. params: { land_type: landTypeChinese },
  229. responseType: 'blob' // 接收二进制数据
  230. });
  231. // 创建对象URL
  232. return URL.createObjectURL(response.data);
  233. } catch (error) {
  234. console.error('获取默认地图失败:', error);
  235. ElMessage.error('获取地图失败,请重试');
  236. return '';
  237. }
  238. };
  239. // 获取默认直方图
  240. const fetchDefaultHistogram = async (landTypeChinese) => {
  241. try {
  242. const response = await axios.get('http://localhost:8000/api/water/default-histogram', {
  243. params: { land_type: landTypeChinese },
  244. responseType: 'blob' // 接收二进制数据
  245. });
  246. // 创建对象URL
  247. return URL.createObjectURL(response.data);
  248. } catch (error) {
  249. console.error('获取默认直方图失败:', error);
  250. ElMessage.error('获取直方图失败,请重试');
  251. return '';
  252. }
  253. };
  254. // 获取统计数据
  255. const fetchStatistics = async (landTypeChinese) => {
  256. try {
  257. const response = await axios.get('http://localhost:8000/api/water/statistics', {
  258. params: { land_type: landTypeChinese }
  259. });
  260. return response.data;
  261. } catch (error) {
  262. console.error('获取统计数据失败:', error);
  263. ElMessage.error('获取统计数据失败,请重试');
  264. return null;
  265. }
  266. };
  267. // 返回计算页面
  268. const backToCalculation = () => {
  269. showCalculation.value = true;
  270. showResults.value = false;
  271. };
  272. const calculateFlux = async () => {
  273. // 检查是否选择了土地类型
  274. if (selectedLandTypes.value.length === 0) {
  275. ElMessage.warning('请至少选择一种土地类型');
  276. return;
  277. }
  278. // 收集所有土地类型的通量计算结果
  279. const landFluxResults = [];
  280. let inputValid = true; // 修复:在循环外部定义验证变量
  281. // 遍历所有选中的土地类型
  282. for (const landType of selectedLandTypes.value) {
  283. let flux = 0;
  284. let usage, efficiency;
  285. // 根据土地类型获取对应的输入值
  286. if (landType === 'water') {
  287. usage = irrigationWaterUsage.value;
  288. efficiency = irrigationEfficiency.value;
  289. } else if (landType === 'irrigated') {
  290. usage = irrigatedWaterUsage.value;
  291. efficiency = irrigatedEfficiency.value;
  292. } else if (landType === 'dry') {
  293. usage = dryWaterUsage.value;
  294. efficiency = dryEfficiency.value;
  295. }
  296. // 验证输入有效性
  297. if (!usage || !efficiency) {
  298. ElMessage.warning(`请输入${landTypeMap[landType]}的灌溉水用量和有效利用率`);
  299. inputValid = false;
  300. break;
  301. }
  302. const usageValue = parseFloat(usage);
  303. const efficiencyValue = parseFloat(efficiency) / 100;
  304. if (isNaN(usageValue) || isNaN(efficiencyValue)) {
  305. ElMessage.error(`请输入${landTypeMap[landType]}的有效数字`);
  306. inputValid = false;
  307. break;
  308. } else if (efficiencyValue > 1 || efficiencyValue < 0) {
  309. ElMessage.error(`${landTypeMap[landType]}的有效利用率应在0-100%之间`);
  310. inputValid = false;
  311. break;
  312. }
  313. // 计算通量
  314. flux = usageValue * efficiencyValue;
  315. landFluxResults.push({
  316. landType: landTypeMap[landType],
  317. flux,
  318. efficiency: efficiencyValue
  319. });
  320. }
  321. if (!inputValid) return; // 使用在外部定义的验证变量
  322. loading.value = true;
  323. try {
  324. // 准备调用后端接口的数据
  325. const formData = new FormData();
  326. // 构建土地类型列表(中文名称)
  327. const landTypesChinese = selectedLandTypes.value.map(type => landTypeMap[type]);
  328. // 重要修改:将 land_types 作为数组传递(JSON字符串)
  329. landTypesChinese.forEach(landType => formData.append('land_types', landType));
  330. // 构建系数参数对象(每个土地类型对应的系数)
  331. const coefficientParams = {};
  332. landFluxResults.forEach(result => {
  333. coefficientParams[result.landType] = [result.flux, result.efficiency];
  334. });
  335. formData.append('coefficient_params', JSON.stringify(coefficientParams));
  336. // 其他固定参数
  337. formData.append('color_map_name', "绿-黄-红-紫");
  338. formData.append('output_size', 8);
  339. // 调用计算接口
  340. await axios.post('http://localhost:8000/api/water/calculate', formData, {
  341. headers: {
  342. 'Content-Type': 'multipart/form-data'
  343. }
  344. });
  345. // 获取并显示结果
  346. // 使用下划线连接的土地类型名称作为参数获取结果
  347. const landTypeParam = landTypesChinese.join('_');
  348. // 获取默认地图
  349. mapImageUrl.value = await fetchDefaultMap(landTypeParam);
  350. // 获取默认直方图
  351. histogramImageUrl.value = await fetchDefaultHistogram(landTypeParam);
  352. // 获取统计数据
  353. statisticsData.value = await fetchStatistics(landTypeParam);
  354. // 切换到结果页面
  355. showCalculation.value = false;
  356. showResults.value = true;
  357. ElMessage.success('计算完成,结果已展示');
  358. } catch (error) {
  359. console.error('获取结果失败:', error);
  360. ElMessage.error('获取结果失败,请重试');
  361. } finally {
  362. loading.value = false;
  363. }
  364. };
  365. return {
  366. selectedLandTypes,
  367. irrigationWaterUsage,
  368. irrigationEfficiency,
  369. irrigatedWaterUsage,
  370. irrigatedEfficiency,
  371. dryWaterUsage,
  372. dryEfficiency,
  373. calculateFlux,
  374. fluxResult,
  375. showCalculation,
  376. showResults,
  377. loading,
  378. mapImageUrl,
  379. histogramImageUrl,
  380. statisticsData,
  381. formatNumber,
  382. backToCalculation
  383. };
  384. }
  385. };
  386. </script>
  387. <style scoped>
  388. /* 保持原有样式不变,添加地图和直方图并列样式 */
  389. .gradient-card {
  390. background: linear-gradient(
  391. 135deg,
  392. rgba(250, 253, 255, 0.8),
  393. rgba(137, 223, 252, 0.8)
  394. );
  395. width: 80%;
  396. max-width: 800px;
  397. padding: 25px;
  398. border-radius: 12px;
  399. }
  400. .results-card {
  401. background: linear-gradient(
  402. 135deg,
  403. rgba(250, 253, 255, 0.8),
  404. rgba(137, 223, 252, 0.8)
  405. );
  406. width: 90%;
  407. max-width: 1200px;
  408. padding: 30px;
  409. border-radius: 12px;
  410. }
  411. .results-header {
  412. display: flex;
  413. justify-content: space-between;
  414. align-items: center;
  415. margin-bottom: 20px;
  416. }
  417. .result-title {
  418. font-size: 24px;
  419. font-weight: bold;
  420. text-align: center;
  421. flex-grow: 1;
  422. }
  423. .input-title {
  424. font-size: 14px;
  425. font-weight: 500;
  426. color: #606266;
  427. margin-bottom: 5px;
  428. text-align: left;
  429. }
  430. .radio-container {
  431. display: flex;
  432. justify-content: flex-start;
  433. align-items: center;
  434. height: 100%;
  435. }
  436. .calculate-btn {
  437. width: 100%;
  438. max-width: 300px;
  439. height: 42px;
  440. border-radius: 8px;
  441. font-size: 1rem;
  442. font-weight: 600;
  443. background: linear-gradient(45deg, #1a8cff, #00cc99);
  444. color: white;
  445. }
  446. .results-container {
  447. margin-top: 20px;
  448. }
  449. .result-subtitle {
  450. text-align: center;
  451. font-weight: bold;
  452. font-size: 20px;
  453. margin-bottom: 15px;
  454. padding-bottom: 5px;
  455. border-bottom: 1px solid #eee;
  456. }
  457. /* 新增:地图和直方图并列样式 */
  458. .image-row {
  459. display: flex;
  460. gap: 20px;
  461. margin-top: 30px;
  462. }
  463. .image-container {
  464. flex: 1;
  465. display: flex;
  466. flex-direction: column;
  467. }
  468. .image-wrapper {
  469. flex: 1;
  470. display: flex;
  471. justify-content: center;
  472. align-items: center;
  473. border: 1px solid #e4e7ed;
  474. border-radius: 8px;
  475. overflow: hidden;
  476. background-color: #f8f8f8;
  477. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  478. height: 400px; /* 固定高度 */
  479. }
  480. .result-image {
  481. max-width: 100%;
  482. max-height: 100%;
  483. object-fit: contain;
  484. }
  485. .image-placeholder {
  486. height: 100%;
  487. width: 100%;
  488. display: flex;
  489. align-items: center;
  490. justify-content: center;
  491. color: #909399;
  492. font-style: italic;
  493. font-size: 18px;
  494. }
  495. .statistics-container {
  496. background-color: rgba(255, 255, 255, 0.7);
  497. border-radius: 12px;
  498. padding: 25px;
  499. }
  500. /* 表格样式增强 */
  501. :deep(.el-table) {
  502. margin-bottom: 25px;
  503. border-radius: 10px;
  504. overflow: hidden;
  505. }
  506. :deep(.el-table__header) {
  507. background-color: #f0f8ff;
  508. }
  509. :deep(.el-table th) {
  510. background-color: #f0f8ff;
  511. font-weight: bold;
  512. }
  513. /* 返回按钮样式 */
  514. .back-button {
  515. position: absolute;
  516. top: 20px;
  517. left: 20px;
  518. width: 120px;
  519. padding: 10px;
  520. background: linear-gradient(to right, #8DF9F0, #26B046);
  521. color: white;
  522. border-radius: 20px;
  523. }
  524. /* 响应式调整 */
  525. @media (max-width: 992px) {
  526. .image-row {
  527. flex-direction: column;
  528. }
  529. .image-container {
  530. width: 100%;
  531. }
  532. }
  533. </style>