soilAcidReductionData.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. <template>
  2. <div class="app-container">
  3. <!-- 操作按钮区 -->
  4. <div class="button-group">
  5. <el-button
  6. :icon="Plus"
  7. type="primary"
  8. @click="openDialog('add')"
  9. class="custom-button add-button"
  10. >新增记录</el-button
  11. >
  12. <el-button
  13. :icon="Download"
  14. type="primary"
  15. @click="downloadTemplateAction"
  16. class="custom-button download-button"
  17. >下载模板</el-button
  18. >
  19. <el-button
  20. :icon="Download"
  21. type="primary"
  22. @click="exportDataAction"
  23. class="custom-button export-button"
  24. >导出数据</el-button
  25. >
  26. <el-upload :before-upload="importDataAction" accept=".xlsx, .csv">
  27. <el-button
  28. :icon="Upload"
  29. type="primary"
  30. class="custom-button import-button"
  31. >导入数据</el-button
  32. >
  33. </el-upload>
  34. </div>
  35. <!-- 数据表格 -->
  36. <el-table
  37. :data="pagedTableDataWithIndex"
  38. fit
  39. style="width: 100%"
  40. @row-click="handleRowClick"
  41. highlight-current-row
  42. class="custom-table"
  43. v-loading="loading"
  44. table-layout="auto"
  45. >
  46. <el-table-column
  47. key="displayIndex"
  48. prop="displayIndex"
  49. label="序号"
  50. width="80"
  51. align="center"
  52. ></el-table-column>
  53. <el-table-column
  54. v-for="col in columns.filter((col) => col.key !== 'id')"
  55. :key="col.key"
  56. :prop="col.dataKey"
  57. :label="col.title"
  58. :min-width="col.title.length * 20 + 40"
  59. :formatter="formatNumber"
  60. align="center"
  61. header-align="center"
  62. ></el-table-column>
  63. <el-table-column label="操作" width="120" align="center">
  64. <template #default="scope">
  65. <span class="action-buttons">
  66. <el-tooltip
  67. class="item"
  68. effect="dark"
  69. content="编辑"
  70. placement="top"
  71. >
  72. <el-button
  73. circle
  74. :icon="EditPen"
  75. @click.stop="openDialog('edit', scope.row)"
  76. class="action-button edit-button"
  77. ></el-button>
  78. </el-tooltip>
  79. <el-tooltip
  80. class="item"
  81. effect="dark"
  82. content="删除"
  83. placement="top"
  84. >
  85. <el-button
  86. circle
  87. :icon="DeleteFilled"
  88. @click.stop="deleteItem(scope.row)"
  89. class="action-button delete-button"
  90. ></el-button>
  91. </el-tooltip>
  92. </span>
  93. </template>
  94. </el-table-column>
  95. </el-table>
  96. <!-- 分页控制 -->
  97. <PaginationComponent
  98. :total="tableData.length"
  99. :currentPage="currentPage4"
  100. :pageSize="pageSize4"
  101. @update:currentPage="currentPage4 = $event"
  102. @update:pageSize="pageSize4 = $event"
  103. @size-change="handleSizeChange"
  104. @current-change="handleCurrentChange"
  105. class="pagination-container"
  106. />
  107. <!-- 新增/编辑对话框 -->
  108. <el-dialog :title="dialogTitle" v-model="dialogVisible" width="50%">
  109. <el-form :model="formData" label-width="150px">
  110. <el-form-item
  111. v-for="col in editableColumns"
  112. :key="col.key"
  113. :label="col.title"
  114. >
  115. <el-input
  116. v-model="formData[col.dataKey]"
  117. :type="col.inputType || 'text'"
  118. class="custom-input"
  119. ></el-input>
  120. </el-form-item>
  121. </el-form>
  122. <template #footer>
  123. <el-button @click="dialogVisible = false" class="custom-cancel-button"
  124. >取消</el-button
  125. >
  126. <el-button
  127. type="primary"
  128. @click="submitForm"
  129. class="custom-submit-button"
  130. >{{ dialogSubmitButtonText }}</el-button
  131. >
  132. </template>
  133. </el-dialog>
  134. </div>
  135. </template>
  136. <script lang="ts" setup>
  137. import { ref, reactive, computed, onMounted } from "vue";
  138. import {
  139. DeleteFilled,
  140. Download,
  141. Upload,
  142. Plus,
  143. EditPen,
  144. } from "@element-plus/icons-vue";
  145. import { ElMessage } from "element-plus";
  146. import {
  147. table,
  148. updateItem,
  149. addItem,
  150. deleteItemApi,
  151. downloadTemplate,
  152. exportData,
  153. importData,
  154. } from "@/API/menus";
  155. import PaginationComponent from "@/components/PaginationComponent.vue";
  156. interface Column {
  157. key: string;
  158. dataKey: string;
  159. title: string;
  160. width: number;
  161. inputType?: string;
  162. }
  163. const columns: Column[] = [
  164. { key: "id", dataKey: "id", title: "ID", width: 100 },
  165. { key: "OM", dataKey: "OM", title: "有机质含量", width: 150 },
  166. { key: "CL", dataKey: "CL", title: "土壤粘粒", width: 150 },
  167. { key: "CEC", dataKey: "CEC", title: "阳离子交换量", width: 150 },
  168. { key: "H_plus", dataKey: "H_plus", title: "交换性氢", width: 150 },
  169. { key: "N", dataKey: "N", title: "水解氮", width: 150 },
  170. { key: "Al3_plus", dataKey: "Al3_plus", title: "交换性铝", width: 150 },
  171. { key: "Delta_pH", dataKey: "Delta_pH", title: "ΔpH", width: 170 },
  172. ];
  173. const editableColumns = columns.filter((col) => col.key !== "id");
  174. const tableData = ref<any[]>([]);
  175. const selectedRow = ref<any | null>(null);
  176. const dialogVisible = ref(false);
  177. const formData = reactive<any>({});
  178. const dialogMode = ref<"add" | "edit">("add"); // 新增还是编辑模式
  179. const currentPage4 = ref(1);
  180. const pageSize4 = ref(10);
  181. const pagedTableData = computed(() => {
  182. const start = (currentPage4.value - 1) * pageSize4.value;
  183. const end = start + pageSize4.value;
  184. return tableData.value.slice(start, end);
  185. });
  186. const pagedTableDataWithIndex = computed(() => {
  187. return pagedTableData.value.map((item, index) => ({
  188. ...item,
  189. displayIndex: (currentPage4.value - 1) * pageSize4.value + index + 1,
  190. }));
  191. });
  192. const loading = ref(false);
  193. const currentTableName = "current_reflux";
  194. const fetchTable = async () => {
  195. console.log("正在获取表格数据...");
  196. try {
  197. loading.value = true;
  198. const response = await table({ table: currentTableName });
  199. console.log("获取到的数据:", response);
  200. tableData.value = response.data.rows;
  201. } catch (error) {
  202. console.error("获取数据时出错:", error);
  203. ElMessage.error("获取数据失败,请检查网络连接或服务器状态");
  204. } finally {
  205. loading.value = false;
  206. }
  207. };
  208. onMounted(() => {
  209. fetchTable();
  210. });
  211. const handleRowClick = (row: any) => {
  212. selectedRow.value = row;
  213. Object.assign(formData, row);
  214. };
  215. const openDialog = (mode: "add" | "edit", row?: any) => {
  216. console.log(`${mode === "add" ? "打开新增记录" : "打开编辑记录"}对话框`);
  217. if (mode === "add") {
  218. selectedRow.value = null;
  219. editableColumns.forEach((col) => {
  220. formData[col.dataKey] = "";
  221. });
  222. } else if (row) {
  223. console.log("编辑记录:", row);
  224. selectedRow.value = row;
  225. Object.assign(formData, row);
  226. }
  227. dialogMode.value = mode;
  228. dialogVisible.value = true;
  229. };
  230. function prepareFormData(
  231. formData: { [x: string]: any },
  232. excludeKeys = ["displayIndex"]
  233. ): { [x: string]: any } {
  234. const result: { [x: string]: any } = {};
  235. for (const key in formData) {
  236. if (!excludeKeys.includes(key)) {
  237. let value = formData[key];
  238. if (typeof value === "string" && value.startsWith("displayIndex-")) {
  239. value = value.replace("displayIndex-", "");
  240. }
  241. result[key] = value;
  242. }
  243. }
  244. return result;
  245. }
  246. const submitForm = async () => {
  247. console.log("开始提交表单...");
  248. try {
  249. const isValid = validateFormData(formData);
  250. if (!isValid) {
  251. console.error("表单验证失败,请检查输入的数据");
  252. alert("请检查输入的数据");
  253. return;
  254. }
  255. const dataToSubmit = prepareFormData(formData);
  256. if (!dataToSubmit.id && dialogMode.value !== "add") {
  257. console.error("无法找到记录ID,请联系管理员");
  258. alert("无法找到记录ID,请联系管理员");
  259. return;
  260. }
  261. let response;
  262. if (dialogMode.value === "add") {
  263. console.log("正在添加新记录...");
  264. response = await addItem({ table: currentTableName, item: dataToSubmit });
  265. } else {
  266. console.log("正在更新现有记录...");
  267. response = await updateItem({
  268. table: currentTableName,
  269. item: dataToSubmit,
  270. });
  271. }
  272. console.log(
  273. dialogMode.value === "add" ? "添加响应:" : "更新响应:",
  274. response
  275. );
  276. dialogVisible.value = false;
  277. fetchTable();
  278. alert(dialogMode.value === "add" ? "添加成功" : "修改成功");
  279. } catch (error) {
  280. console.error("提交表单时发生错误:", error);
  281. let errorMessage = "未知错误";
  282. if (error && typeof error === "object" && "response" in error) {
  283. const response = (error as { response?: { data?: { message?: string } } })
  284. .response;
  285. if (
  286. response &&
  287. response.data &&
  288. typeof response.data.message === "string"
  289. ) {
  290. errorMessage = response.data.message;
  291. }
  292. }
  293. console.error(`提交失败原因: ${errorMessage}`);
  294. alert(`提交失败: ${errorMessage}`);
  295. }
  296. };
  297. function validateFormData(data: { [x: string]: undefined }) {
  298. for (let key in data) {
  299. if (data[key] === "" || data[key] === undefined) {
  300. return false;
  301. }
  302. }
  303. return true;
  304. }
  305. const deleteItem = async (row: any) => {
  306. console.log("准备删除记录:", row);
  307. if (!row) {
  308. ElMessage.warning("请先选择一行记录");
  309. return;
  310. }
  311. try {
  312. const condition = { id: row.id };
  313. await deleteItemApi({ table: "current_reflux", condition });
  314. const index = tableData.value.findIndex((item) => item.id === row.id);
  315. if (index > -1) {
  316. tableData.value.splice(index, 1);
  317. }
  318. fetchTable();
  319. console.log("记录删除成功");
  320. } catch (error) {
  321. console.error("删除记录时发生错误:", error);
  322. }
  323. };
  324. const downloadTemplateAction = async () => {
  325. try {
  326. await downloadTemplate("current_reflux");
  327. } catch (error) {
  328. console.error("下载模板时发生错误:", error);
  329. }
  330. };
  331. const exportDataAction = async () => {
  332. try {
  333. await exportData("current_reflux");
  334. } catch (error) {
  335. console.error("导出数据时发生错误:", error);
  336. }
  337. };
  338. const importDataAction = async (file: File) => {
  339. try {
  340. const response = await importData("reduce", file); // 传递 dataset_type 的值(如 'reduce')
  341. if (response && response.data) {
  342. const { total_data, new_data, duplicate_data, message } = response.data;
  343. ElMessage({
  344. message: `导入结果: ${message} 新增总数:${total_data}, 成功新增:${new_data}, 数据重复:${duplicate_data}`,
  345. type: "success",
  346. });
  347. fetchTable(); // 假设存在 fetchTable 方法刷新表格
  348. }
  349. } catch (error) {
  350. let errorMessage = "数据导入失败";
  351. if (error && typeof error === "object" && "response" in error) {
  352. const response = (error as { response?: { data?: { message?: string } } })
  353. .response;
  354. if (response && response.data && typeof response.data.message === "string") {
  355. errorMessage += `: ${response.data.message}`;
  356. }
  357. }
  358. ElMessage.error(errorMessage);
  359. }
  360. };
  361. const handleFileChange = (event: Event) => {
  362. const target = event.target as HTMLInputElement;
  363. if (target.files && target.files.length > 0) {
  364. importDataAction(target.files[0]);
  365. }
  366. };
  367. const handleSizeChange = (val: number) => {};
  368. const handleCurrentChange = (val: number) => {};
  369. const formatNumber = (row: any, column: any, cellValue: any) => {
  370. if (typeof cellValue === "number") {
  371. return cellValue.toFixed(3);
  372. }
  373. return cellValue;
  374. };
  375. const dialogTitle = computed(() => {
  376. return dialogMode.value === "add" ? "新增记录" : "编辑记录";
  377. });
  378. const dialogSubmitButtonText = computed(() => {
  379. return dialogMode.value === "add" ? "添加" : "保存";
  380. });
  381. </script>
  382. <style scoped>
  383. .app-container {
  384. padding: 20px 40px;
  385. min-height: 100vh;
  386. background-color: #f0f2f5;
  387. box-sizing: border-box;
  388. display: flex;
  389. flex-direction: column;
  390. align-items: center;
  391. }
  392. .button-group {
  393. display: flex;
  394. gap: 12px;
  395. justify-content: flex-end;
  396. width: 100%;
  397. margin-bottom: 20px;
  398. }
  399. .custom-button {
  400. color: #fff;
  401. border: none;
  402. border-radius: 8px;
  403. font-size: 14px;
  404. padding: 10px 18px;
  405. transition: transform 0.3s ease, background-color 0.3s ease;
  406. min-width: 130px;
  407. display: flex;
  408. align-items: center;
  409. justify-content: center;
  410. }
  411. .download-button,
  412. .export-button,
  413. .add-button,
  414. .import-button {
  415. background-color: #67c23a;
  416. }
  417. .download-button:hover,
  418. .export-button:hover,
  419. .import-button:hover,
  420. .add-button:hover {
  421. background-color: #85ce61;
  422. transform: scale(1.05);
  423. }
  424. .custom-table {
  425. width: 100%;
  426. border-radius: 8px;
  427. overflow: hidden;
  428. box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
  429. background-color: #fff;
  430. margin-top: 10px;
  431. }
  432. :deep( .el-table th) {
  433. background: linear-gradient(180deg, #61e054, #4db944);
  434. color: #fff;
  435. font-weight: bold;
  436. text-align: center;
  437. padding: 14px 0;
  438. font-size: 14px;
  439. }
  440. :deep( .el-table__row:nth-child(odd)) {
  441. background-color: #E4FBE5;
  442. }
  443. :deep( .el-table__row:nth-child(even)) {
  444. background-color: #ffffff;
  445. }
  446. :deep( .el-table td) {
  447. padding: 12px 10px;
  448. text-align: center;
  449. border-bottom: 1px solid #ebeef5;
  450. }
  451. .action-button {
  452. font-size: 16px;
  453. margin-right: 5px;
  454. border-radius: 50%;
  455. transition: transform 0.3s ease, background-color 0.3s ease;
  456. }
  457. .edit-button {
  458. background-color: #409eff;
  459. color: #fff;
  460. }
  461. .edit-button:hover {
  462. background-color: #66b1ff;
  463. transform: scale(1.1);
  464. }
  465. .delete-button {
  466. background-color: #f56c6c;
  467. color: #fff;
  468. }
  469. .delete-button:hover {
  470. background-color: #f78989;
  471. transform: scale(1.1);
  472. }
  473. .el-form-item__label {
  474. width: 150px;
  475. font-weight: bold;
  476. }
  477. .el-form-item__content {
  478. margin-left: 150px;
  479. }
  480. .custom-input .el-input__inner {
  481. border-radius: 6px;
  482. border: 1px solid #dcdcdc;
  483. transition: border-color 0.3s ease;
  484. padding: 10px;
  485. font-size: 14px;
  486. }
  487. .custom-input .el-input__inner:focus {
  488. border-color: #409eff;
  489. }
  490. .custom-cancel-button,
  491. .custom-submit-button {
  492. border: none;
  493. border-radius: 6px;
  494. font-size: 14px;
  495. padding: 10px 20px;
  496. transition: transform 0.3s ease, background-color 0.3s ease;
  497. min-width: 100px;
  498. }
  499. .custom-cancel-button {
  500. background-color: #f4f4f5;
  501. color: #606266;
  502. }
  503. .custom-cancel-button:hover {
  504. background-color: #e4e7ed;
  505. transform: scale(1.05);
  506. }
  507. .custom-submit-button {
  508. background-color: #409eff;
  509. color: #fff;
  510. }
  511. .custom-submit-button:hover {
  512. background-color: #66b1ff;
  513. transform: scale(1.05);
  514. }
  515. .pagination-container {
  516. margin-top: 30px;
  517. text-align: center;
  518. }
  519. .action-buttons {
  520. display: flex;
  521. justify-content: center;
  522. }
  523. </style>