irrigationWater.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673
  1. <template>
  2. <div class="page-container">
  3. <<<<<<< HEAD
  4. <div>
  5. <router-view></router-view> <!-- 关键:子路由渲染位置 -->
  6. </div>
  7. =======
  8. <!-- 上半部分:地图 + 柱状图 -->
  9. <div class="top-content">
  10. <!-- 灌溉水输入通量计算模块 -->
  11. <div class="irrigation-form">
  12. <div class="form-header">
  13. <h3>灌溉水输入通量计算</h3>
  14. </div>
  15. <div class="form-body">
  16. <div class="land-form-row">
  17. <!-- 水田 -->
  18. <div class="land-form">
  19. <h4>水田</h4>
  20. <el-form :model="irrigationData.paddy" label-position="top">
  21. <el-form-item label="灌溉水用量 (吨/公顷)">
  22. <el-input-number
  23. v-model="irrigationData.paddy.usage"
  24. :min="0"
  25. :step="100"
  26. ></el-input-number>
  27. </el-form-item>
  28. <el-form-item label="灌溉水有效利用率 (%)">
  29. <el-input-number
  30. v-model="irrigationData.paddy.efficiency"
  31. :min="0"
  32. :max="100"
  33. :step="5"
  34. ></el-input-number>
  35. </el-form-item>
  36. <el-form-item label="计算通量(吨/公顷·年)">
  37. <el-input
  38. v-model="irrigationData.paddy.flux"
  39. readonly
  40. class="flux-input"
  41. >
  42. </el-input>
  43. </el-form-item>
  44. <el-button
  45. type="primary"
  46. @click="calculateFlux('paddy')"
  47. class="calculate-btn"
  48. >计算水田通量</el-button>
  49. </el-form>
  50. </div>
  51. <!-- 水浇地 -->
  52. <div class="land-form">
  53. <h4>水浇地</h4>
  54. <el-form :model="irrigationData.irrigated" label-position="top">
  55. <el-form-item label="灌溉水用量 (吨/公顷)">
  56. <el-input-number
  57. v-model="irrigationData.irrigated.usage"
  58. :min="0"
  59. :step="100"
  60. ></el-input-number>
  61. </el-form-item>
  62. <el-form-item label="灌溉水有效利用率 (%)">
  63. <el-input-number
  64. v-model="irrigationData.irrigated.efficiency"
  65. :min="0"
  66. :max="100"
  67. :step="5"
  68. ></el-input-number>
  69. </el-form-item>
  70. <el-form-item label="计算通量(吨/公顷·年)">
  71. <el-input
  72. v-model="irrigationData.irrigated.flux"
  73. readonly
  74. class="flux-input"
  75. >
  76. </el-input>
  77. </el-form-item>
  78. <el-button
  79. type="primary"
  80. @click="calculateFlux('irrigated')"
  81. class="calculate-btn"
  82. >计算水浇地通量</el-button>
  83. </el-form>
  84. </div>
  85. <!-- 旱地 -->
  86. <div class="land-form">
  87. <h4>旱地</h4>
  88. <el-form :model="irrigationData.dryland" label-position="top">
  89. <el-form-item label="灌溉水用量 (吨/公顷)">
  90. <el-input-number
  91. v-model="irrigationData.dryland.usage"
  92. :min="0"
  93. :step="100"
  94. ></el-input-number>
  95. </el-form-item>
  96. <el-form-item label="灌溉水有效利用率 (%)">
  97. <el-input-number
  98. v-model="irrigationData.dryland.efficiency"
  99. :min="0"
  100. :max="100"
  101. :step="5"
  102. ></el-input-number>
  103. </el-form-item>
  104. <el-form-item label="计算通量(吨/公顷·年)">
  105. <el-input
  106. v-model="irrigationData.dryland.flux"
  107. readonly
  108. class="flux-input"
  109. >
  110. </el-input>
  111. </el-form-item>
  112. <el-button
  113. type="primary"
  114. @click="calculateFlux('dryland')"
  115. class="calculate-btn"
  116. >计算旱地通量</el-button>
  117. </el-form>
  118. </div>
  119. </div>
  120. </div>
  121. </div>
  122. <!-- 右侧图表区域 -->
  123. <div class="graphics-container">
  124. <!-- 下拉选择栏 -->
  125. <div class="land-type-selector">
  126. <el-select v-model="selectedLandType" placeholder="选择土地类型" @change="handleLandTypeChange">
  127. <el-option label="水田" value="paddy"></el-option>
  128. <el-option label="水浇地" value="irrigated"></el-option>
  129. <el-option label="旱地" value="dryland"></el-option>
  130. </el-select>
  131. </div>
  132. <!-- 图表容器 -->
  133. <div class="graphics-row">
  134. <!-- 地图模块 -->
  135. <div class="map-module">
  136. <div v-if="rasterMapImage" class="map-box">
  137. <img :src="rasterMapImage" alt="土地类型Cd分布图" class="raster-image">
  138. </div>
  139. <div v-else-if="mapLoading" class="map-box">地图加载中...</div>
  140. <div v-else class="map-box error-message">
  141. 地图加载失败: {{ mapError }}
  142. <el-button type="primary" size="small" @click="retryMap">重试</el-button>
  143. </div>
  144. </div>
  145. <!-- 图表模块 -->
  146. <div class="chart-module">
  147. <div v-if="histogramImage" class="chart-box">
  148. <img :src="histogramImage" alt="数据分布直方图" class="histogram-image">
  149. </div>
  150. <div v-else-if="histogramLoading" class="chart-box">直方图加载中...</div>
  151. <div v-else class="chart-box error-message">
  152. 直方图加载失败: {{ histogramError }}
  153. <el-button type="primary" size="small" @click="retryHistogram">重试</el-button>
  154. </div>
  155. </div>
  156. </div>
  157. </div>
  158. </div>
  159. <!-- 中间间距 -->
  160. <div class="middle-gap"></div>
  161. <!-- 下半部分:表格 -->
  162. <div class="table-module">
  163. <div class="table-header">
  164. <h3>计算结果汇总</h3>
  165. <el-button type="primary" @click="exportData">导出数据</el-button>
  166. </div>
  167. <el-table
  168. :data="tableData"
  169. style="width: 100%"
  170. border
  171. stripe
  172. class="compact-table"
  173. >
  174. <el-table-column prop="name" label="土地类型" width="120" />
  175. <el-table-column prop="area" label="面积 (公顷)" width="100" />
  176. <el-table-column prop="quality" label="质量等级" width="100" />
  177. <el-table-column prop="productivity" label="生产力指数" width="120" />
  178. <el-table-column prop="waterUsage" label="灌溉水用量" width="120" />
  179. <el-table-column prop="waterEfficiency" label="利用率" width="100" />
  180. <el-table-column prop="waterFlux" label="通量" width="120" />
  181. <el-table-column label="操作" width="80">
  182. <template #default="{ row }">
  183. <el-button type="primary" size="small" @click="editItem(row)">修改</el-button>
  184. </template>
  185. </el-table-column>
  186. </el-table>
  187. </div>
  188. >>>>>>> ding
  189. </div>
  190. </template>
  191. <script setup>
  192. import { ref, onMounted, reactive, watch } from 'vue';
  193. import axios from 'axios';
  194. // 配置API基础URL
  195. const BASE_URL = 'http://localhost:8000/api/water';
  196. // 创建自定义axios实例
  197. const http = axios.create({
  198. timeout: 60000, // 60秒超时
  199. });
  200. // 土地类型数据映射
  201. const landTypeData = {
  202. paddy: {
  203. shp_base_name: 'lechang',
  204. tif_type: '水田',
  205. properties: {
  206. area: 1200,
  207. quality: '优',
  208. productivity: 850
  209. }
  210. },
  211. dryland: {
  212. shp_base_name: 'lechang',
  213. tif_type: '旱地',
  214. properties: {
  215. area: 800,
  216. quality: '良',
  217. productivity: 600
  218. }
  219. },
  220. irrigated: {
  221. shp_base_name: 'lechang',
  222. tif_type: '水浇地',
  223. properties: {
  224. area: 950,
  225. quality: '中',
  226. productivity: 720
  227. }
  228. }
  229. };
  230. // 灌溉水计算数据
  231. const irrigationData = reactive({
  232. paddy: {
  233. usage: 8000,
  234. efficiency: 60,
  235. flux: 4800
  236. },
  237. irrigated: {
  238. usage: 6000,
  239. efficiency: 70,
  240. flux: 4200
  241. },
  242. dryland: {
  243. usage: 4000,
  244. efficiency: 50,
  245. flux: 2000
  246. }
  247. });
  248. // 响应式数据
  249. const selectedLandType = ref('paddy');
  250. const rasterMapImage = ref(null);
  251. const histogramImage = ref(null);
  252. const tableData = ref([]);
  253. // 加载状态和错误信息
  254. const mapLoading = ref(false);
  255. const histogramLoading = ref(false);
  256. const mapError = ref('');
  257. const histogramError = ref('');
  258. // 初始化数据和表格
  259. const initTableData = () => {
  260. tableData.value = [
  261. {
  262. id: 1,
  263. name: '水田',
  264. ...landTypeData.paddy.properties,
  265. waterUsage: irrigationData.paddy.usage,
  266. waterEfficiency: irrigationData.paddy.efficiency + '%',
  267. waterFlux: irrigationData.paddy.flux
  268. },
  269. {
  270. id: 2,
  271. name: '水浇地',
  272. ...landTypeData.irrigated.properties,
  273. waterUsage: irrigationData.irrigated.usage,
  274. waterEfficiency: irrigationData.irrigated.efficiency + '%',
  275. waterFlux: irrigationData.irrigated.flux
  276. },
  277. {
  278. id: 3,
  279. name: '旱地',
  280. ...landTypeData.dryland.properties,
  281. waterUsage: irrigationData.dryland.usage,
  282. waterEfficiency: irrigationData.dryland.efficiency + '%',
  283. waterFlux: irrigationData.dryland.flux
  284. }
  285. ];
  286. };
  287. // 计算灌溉水通量
  288. const calculateFlux = (landType) => {
  289. const data = irrigationData[landType];
  290. data.flux = Math.round(data.usage * data.efficiency / 100);
  291. // 更新表格数据
  292. const row = tableData.value.find(item =>
  293. landType === 'paddy' ? item.name === '水田' :
  294. landType === 'irrigated' ? item.name === '水浇地' :
  295. item.name === '旱地'
  296. );
  297. if (row) {
  298. row.waterUsage = data.usage;
  299. row.waterEfficiency = data.efficiency + '%';
  300. row.waterFlux = data.flux;
  301. }
  302. };
  303. // 导出数据功能
  304. const exportData = () => {
  305. alert('导出数据功能已触发,数据已准备好下载');
  306. // 实际应用中这里会生成并下载CSV文件
  307. };
  308. // 修改条目功能
  309. const editItem = (row) => {
  310. alert(`正在修改 ${row.name} 的数据`);
  311. // 实际应用中这里会打开编辑模态框
  312. };
  313. onMounted(() => {
  314. initTableData();
  315. handleLandTypeChange();
  316. });
  317. // 监听灌溉数据变化更新表格
  318. watch(irrigationData, (newVal) => {
  319. tableData.value = tableData.value.map(item => {
  320. if (item.name === '水田') {
  321. return {...item,
  322. waterUsage: newVal.paddy.usage,
  323. waterEfficiency: newVal.paddy.efficiency + '%',
  324. waterFlux: newVal.paddy.flux};
  325. } else if (item.name === '水浇地') {
  326. return {...item,
  327. waterUsage: newVal.irrigated.usage,
  328. waterEfficiency: newVal.irrigated.efficiency + '%',
  329. waterFlux: newVal.irrigated.flux};
  330. } else {
  331. return {...item,
  332. waterUsage: newVal.dryland.usage,
  333. waterEfficiency: newVal.dryland.efficiency + '%',
  334. waterFlux: newVal.dryland.flux};
  335. }
  336. });
  337. }, { deep: true });
  338. const handleLandTypeChange = async () => {
  339. const landType = selectedLandType.value;
  340. const data = landTypeData[landType];
  341. // 重置状态
  342. rasterMapImage.value = null;
  343. histogramImage.value = null;
  344. mapError.value = '';
  345. histogramError.value = '';
  346. try {
  347. // 设置加载状态
  348. mapLoading.value = true;
  349. histogramLoading.value = true;
  350. // 顺序请求,避免同时处理大文件导致问题
  351. await generateRasterMap(data.shp_base_name, data.tif_type);
  352. await generateHistogram(data.tif_type);
  353. } catch (error) {
  354. console.error('数据加载失败:', error);
  355. } finally {
  356. // 重置加载状态
  357. mapLoading.value = false;
  358. histogramLoading.value = false;
  359. }
  360. };
  361. const generateRasterMap = async (shp_base_name, tif_type) => {
  362. try {
  363. const formData = new FormData();
  364. formData.append('shp_base_name', shp_base_name);
  365. formData.append('tif_type', tif_type);
  366. formData.append('color_map_name', 'colormap6');
  367. formData.append('title_name', `${getLandTypeName(selectedLandType.value)}Cd分布图`);
  368. formData.append('output_size', '8'); // 减小输出尺寸
  369. const response = await http.post(`${BASE_URL}/generate-raster-map`, formData, {
  370. headers: { 'Content-Type': 'multipart/form-data' },
  371. responseType: 'blob'
  372. });
  373. if (response.data.size === 0) {
  374. throw new Error('后端返回空响应');
  375. }
  376. const blob = new Blob([response.data], { type: 'image/jpeg' });
  377. rasterMapImage.value = URL.createObjectURL(blob);
  378. mapError.value = '';
  379. } catch (error) {
  380. console.error('栅格地图生成失败:', error);
  381. mapError.value = error.response?.data?.message || error.message || '未知错误';
  382. throw error;
  383. }
  384. };
  385. const generateHistogram = async (tif_type) => {
  386. try {
  387. const formData = new FormData();
  388. formData.append('tif_type', tif_type);
  389. formData.append('figsize_width', '8'); // 减小宽度
  390. formData.append('figsize_height', '6'); // 减小高度
  391. formData.append('xlabel', '像元值');
  392. formData.append('ylabel', '频率密度');
  393. formData.append('title', `${getLandTypeName(selectedLandType.value)}数据分布`);
  394. const response = await http.post(`${BASE_URL}/generate-tif-histogram`, formData, {
  395. headers: { 'Content-Type': 'multipart/form-data' },
  396. responseType: 'blob'
  397. });
  398. if (response.data.size === 0) {
  399. throw new Error('后端返回空响应');
  400. }
  401. const blob = new Blob([response.data], { type: 'image/jpeg' });
  402. histogramImage.value = URL.createObjectURL(blob);
  403. histogramError.value = '';
  404. } catch (error) {
  405. console.error('直方图生成失败:', error);
  406. histogramError.value = error.response?.data?.message || error.message || '未知错误';
  407. throw error;
  408. }
  409. };
  410. const getLandTypeName = (value) => {
  411. switch (value) {
  412. case 'paddy': return '水田';
  413. case 'dryland': return '旱地';
  414. case 'irrigated': return '水浇地';
  415. default: return '';
  416. }
  417. };
  418. // 重试功能
  419. const retryMap = async () => {
  420. const landType = selectedLandType.value;
  421. const data = landTypeData[landType];
  422. mapLoading.value = true;
  423. mapError.value = '';
  424. try {
  425. await generateRasterMap(data.shp_base_name, data.tif_type);
  426. } catch (error) {
  427. console.error('重试栅格地图失败:', error);
  428. } finally {
  429. mapLoading.value = false;
  430. }
  431. };
  432. const retryHistogram = async () => {
  433. const landType = selectedLandType.value;
  434. const data = landTypeData[landType];
  435. histogramLoading.value = true;
  436. histogramError.value = '';
  437. try {
  438. await generateHistogram(data.tif_type);
  439. } catch (error) {
  440. console.error('重试直方图失败:', error);
  441. } finally {
  442. histogramLoading.value = false;
  443. }
  444. };
  445. </script>
  446. <style scoped>
  447. .page-container {
  448. display: flex;
  449. flex-direction: column;
  450. height: 100vh;
  451. padding: 20px;
  452. box-sizing: border-box;
  453. background-color: #f5f7fa;
  454. }
  455. .top-content {
  456. display: flex;
  457. height: 45%;
  458. gap: 20px; /* 增加间距 */
  459. }
  460. .irrigation-form {
  461. flex: 0 0 45%;
  462. background: white;
  463. border-radius: 12px;
  464. box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
  465. display: flex;
  466. flex-direction: column;
  467. }
  468. .form-header {
  469. background-color: #2c3e50;
  470. color: white;
  471. padding: 10px 15px;
  472. text-align: center;
  473. font-size: 16px;
  474. font-weight: bold;
  475. }
  476. .form-body {
  477. padding: 15px;
  478. flex: 1;
  479. display: flex;
  480. flex-direction: column;
  481. }
  482. .land-form-row {
  483. display: flex;
  484. gap: 20px; /* 增加间距 */
  485. height: 100%;
  486. }
  487. .land-form {
  488. flex: 1;
  489. border: 1px solid #e1e4e8;
  490. border-radius: 8px;
  491. padding: 15px; /* 增加内边距 */
  492. box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
  493. }
  494. .land-form h4 {
  495. margin-top: 0;
  496. margin-bottom: 15px; /* 增加下边距 */
  497. color: #1a73e8;
  498. border-bottom: 1px solid #e1e4e8;
  499. padding-bottom: 10px; /* 增加下边距 */
  500. font-size: 14px;
  501. }
  502. /* 修复通量数字显示不全 */
  503. .flux-input {
  504. width: 100%; /* 确保输入框宽度填满 */
  505. }
  506. .flux-input >>> .el-input__inner {
  507. text-align: left; /* 数字右对齐 */
  508. padding-right: 10px; /* 增加右边距 */
  509. font-weight: bold; /* 加粗显示 */
  510. }
  511. .calculate-btn {
  512. margin-top: 15px; /* 增加按钮上边距 */
  513. }
  514. .graphics-container {
  515. flex: 1;
  516. display: flex;
  517. flex-direction: column;
  518. gap: 15px; /* 增加间距 */
  519. }
  520. .land-type-selector {
  521. margin-bottom: 15px; /* 增加下边距 */
  522. }
  523. .graphics-row {
  524. display: flex;
  525. gap: 15px; /* 增加间距 */
  526. height: calc(100% - 45px); /* 减去选择器高度 */
  527. }
  528. .map-module, .chart-module {
  529. flex: 1;
  530. background: white;
  531. border-radius: 8px;
  532. box-shadow: 0 0 8px rgba(0, 0, 0, 0.1);
  533. display: flex;
  534. justify-content: center;
  535. align-items: center;
  536. overflow: hidden;
  537. height: 100%; /* 固定高度 */
  538. }
  539. .map-box, .chart-box {
  540. width: 100%;
  541. height: 100%;
  542. display: flex;
  543. justify-content: center;
  544. align-items: center;
  545. position: relative;
  546. padding: 10px; /* 增加内边距 */
  547. }
  548. .raster-image, .histogram-image {
  549. width: 100%; /* 宽度固定为容器宽度 */
  550. height: 100%; /* 高度固定为容器高度 */
  551. object-fit: contain; /* 保持比例 */
  552. }
  553. .error-message {
  554. color: #f56c6c;
  555. font-size: 13px;
  556. padding: 10px; /* 增加内边距 */
  557. text-align: center;
  558. display: flex;
  559. flex-direction: column;
  560. align-items: center;
  561. gap: 10px; /* 增加间距 */
  562. }
  563. .middle-gap {
  564. height: 20px; /* 增加中间间距高度 */
  565. }
  566. .table-module {
  567. height: 50%;
  568. background: white;
  569. border-radius: 12px;
  570. box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
  571. display: flex;
  572. flex-direction: column;
  573. margin-top: 50px; /* 增加上边距 */
  574. }
  575. .table-header {
  576. display: flex;
  577. justify-content: space-between;
  578. align-items: center;
  579. padding: 15px 20px; /* 增加内边距 */
  580. background-color: #f8f9fa;
  581. border-bottom: 1px solid #e1e4e8;
  582. }
  583. .table-header h3 {
  584. margin: 0;
  585. font-size: 16px; /* 增加字体大小 */
  586. color: #2c3e50;
  587. }
  588. .compact-table {
  589. width: 100%;
  590. height: 100%;
  591. font-size: 13px;
  592. }
  593. .compact-table >>> .el-table__cell {
  594. padding: 10px 0; /* 增加单元格内边距 */
  595. }
  596. /* 修复表格中数字显示 */
  597. .compact-table >>> .el-table__cell .cell {
  598. padding: 0 10px; /* 增加单元格内边距 */
  599. text-align: center; /* 居中显示 */
  600. white-space: nowrap; /* 防止换行 */
  601. overflow: hidden; /* 隐藏溢出 */
  602. text-overflow: ellipsis; /* 显示省略号 */
  603. }
  604. </style>