irrigationWaterSampleData.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942
  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
  27. :auto-upload="false"
  28. :on-change="handleFileSelect"
  29. accept=".xlsx, .csv"
  30. class="import-upload"
  31. >
  32. <el-button
  33. :icon="Upload"
  34. type="primary"
  35. class="custom-button import-button"
  36. >导入数据</el-button
  37. >
  38. </el-upload>
  39. </div>
  40. <!-- 数据表格:仅数字字段NaN显示0,文字字段不处理 -->
  41. <el-table
  42. :data="pagedTableDataWithIndex"
  43. fit
  44. style="width: 100%"
  45. @row-click="handleRowClick"
  46. highlight-current-row
  47. class="custom-table"
  48. v-loading="loading"
  49. table-layout="auto"
  50. >
  51. <el-table-column
  52. key="displayIndex"
  53. prop="displayIndex"
  54. label="序号"
  55. width="80"
  56. align="center"
  57. ></el-table-column>
  58. <el-table-column
  59. v-for="col in columns.filter((col) => col.key !== 'id')"
  60. :key="col.key"
  61. :prop="col.dataKey"
  62. :label="col.title"
  63. :min-width="col.title.length * 20 + 40"
  64. :formatter="formatTableValue"
  65. align="center"
  66. header-align="center"
  67. ></el-table-column>
  68. <el-table-column label="操作" width="120" align="center">
  69. <template #default="scope">
  70. <span class="action-buttons">
  71. <el-tooltip
  72. class="item"
  73. effect="dark"
  74. content="编辑"
  75. placement="top"
  76. >
  77. <el-button
  78. circle
  79. :icon="EditPen"
  80. @click.stop="openDialog('edit', scope.row)"
  81. class="action-button edit-button"
  82. ></el-button
  83. ></el-tooltip>
  84. <el-tooltip
  85. class="item"
  86. effect="dark"
  87. content="删除"
  88. placement="top"
  89. >
  90. <el-button
  91. circle
  92. :icon="DeleteFilled"
  93. @click.stop="deleteItem(scope.row)"
  94. class="action-button delete-button"
  95. ></el-button
  96. ></el-tooltip>
  97. </span>
  98. </template>
  99. </el-table-column>
  100. </el-table>
  101. <!-- 分页控制 -->
  102. <PaginationComponent
  103. :total="tableData.length"
  104. :currentPage="currentPage4"
  105. :pageSize="pageSize4"
  106. @update:currentPage="currentPage4 = $event"
  107. @update:pageSize="pageSize4 = $event"
  108. @size-change="handleSizeChange"
  109. @current-change="handleCurrentChange"
  110. class="pagination-container"
  111. />
  112. <!-- 新增/编辑对话框:优化样式 -->
  113. <el-dialog
  114. :title="dialogTitle"
  115. v-model="dialogVisible"
  116. width="50%"
  117. :custom-class="['custom-dialog']"
  118. :close-on-click-modal="false"
  119. center
  120. >
  121. <div class="dialog-header">
  122. <h2>{{ dialogTitle }}</h2>
  123. <div class="header-decoration"></div>
  124. </div>
  125. <el-form
  126. ref="formRef"
  127. :model="formData"
  128. label-position="top"
  129. class="custom-dialog-form"
  130. >
  131. <el-form-item
  132. v-for="col in editableColumns"
  133. :key="col.key"
  134. :label="col.title"
  135. class="custom-form-item"
  136. :rules="[
  137. {
  138. required: isTextField(col.dataKey),
  139. message: `请输入${col.title}`,
  140. trigger: 'blur',
  141. },
  142. ]"
  143. >
  144. <el-input
  145. v-model="formData[col.dataKey]"
  146. :type="col.inputType || 'text'"
  147. class="custom-dialog-input"
  148. :placeholder="`请输入${col.title}`"
  149. ></el-input>
  150. </el-form-item>
  151. </el-form>
  152. <template #footer>
  153. <div class="dialog-footer">
  154. <el-button @click="dialogVisible = false" class="custom-cancel-button"
  155. >取消</el-button
  156. >
  157. <el-button
  158. type="primary"
  159. @click="submitForm"
  160. class="custom-submit-button"
  161. >{{ dialogSubmitButtonText }}</el-button
  162. >
  163. </div>
  164. </template>
  165. </el-dialog>
  166. </div>
  167. </template>
  168. <script lang="ts" setup>
  169. import { ref, reactive, computed, onMounted } from "vue";
  170. import {
  171. DeleteFilled,
  172. Download,
  173. Upload,
  174. Plus,
  175. EditPen,
  176. } from "@element-plus/icons-vue";
  177. import { ElMessage, ElForm } from "element-plus";
  178. import {
  179. table,
  180. updateItem,
  181. addItem,
  182. deleteItemApi,
  183. downloadTemplate,
  184. exportData,
  185. importData,
  186. } from "@/API/admin";
  187. import PaginationComponent from "@/components/PaginationComponent.vue";
  188. // 核心:定义字段类型映射(区分文字/数字字段)
  189. const fieldTypeMap: Record<string, "text" | "number"> = {
  190. longitude: "number",
  191. latitude: "number",
  192. company_name: "text",
  193. company_type: "text",
  194. county: "text",
  195. particulate_emission: "number",
  196. };
  197. interface Column {
  198. key: string;
  199. dataKey: string;
  200. title: string;
  201. width: number;
  202. inputType?: string;
  203. }
  204. // 表字段定义(与fieldTypeMap对应)
  205. const columns: Column[] = [
  206. { key: "id", dataKey: "id", title: "ID", width: 80 },
  207. {
  208. key: "longitude",
  209. dataKey: "longitude",
  210. title: "经度坐标",
  211. width: 150,
  212. inputType: "number",
  213. },
  214. {
  215. key: "latitude",
  216. dataKey: "latitude",
  217. title: "纬度坐标",
  218. width: 150,
  219. inputType: "number",
  220. },
  221. {
  222. key: "company_name",
  223. dataKey: "company_name",
  224. title: "企业名称",
  225. width: 200,
  226. inputType: "text",
  227. },
  228. {
  229. key: "company_type",
  230. dataKey: "company_type",
  231. title: "企业类型",
  232. width: 150,
  233. inputType: "text",
  234. },
  235. {
  236. key: "county",
  237. dataKey: "county",
  238. title: "所属区县",
  239. width: 200,
  240. inputType: "text",
  241. },
  242. {
  243. key: "particulate_emission",
  244. dataKey: "particulate_emission",
  245. title: "大气颗粒物排放量(吨/年)",
  246. width: 150,
  247. inputType: "number",
  248. },
  249. ];
  250. const editableColumns = columns.filter((col) => col.key !== "id");
  251. // 核心变量:统一表名
  252. const currentTableName = "atmo_company";
  253. // 表格数据相关
  254. const tableData = ref<any[]>([]);
  255. const selectedRow = ref<any | null>(null);
  256. const loading = ref(false);
  257. const formRef = ref<InstanceType<typeof ElForm> | null>(null); // 表单引用
  258. // 分页相关
  259. const currentPage4 = ref(1);
  260. const pageSize4 = ref(10);
  261. // 对话框相关
  262. const dialogVisible = ref(false);
  263. const formData = reactive<any>({});
  264. const dialogMode = ref<"add" | "edit">("add");
  265. // 辅助函数:判断是否为文字字段
  266. const isTextField = (dataKey: string): boolean => {
  267. return fieldTypeMap[dataKey]?.toLowerCase() === "text";
  268. };
  269. const isNumberField = (dataKey: string): boolean => {
  270. return fieldTypeMap[dataKey]?.toLowerCase() === "number";
  271. };
  272. // 分页和序号处理:仅数字字段处理NaN为0,文字字段保留空
  273. const pagedTableDataWithIndex = computed(() => {
  274. const start = (currentPage4.value - 1) * pageSize4.value;
  275. const end = start + pageSize4.value;
  276. return tableData.value.slice(start, end).map((row, idx) => {
  277. const processedRow = Object.entries(row).reduce((acc, [key, val]) => {
  278. // 仅数字字段:NaN/undefined转为0;文字字段:保留原值(空/文字)
  279. if (isNumberField(key)) {
  280. acc[key] =
  281. (typeof val === "number" && isNaN(val)) || val === undefined
  282. ? 0
  283. : val;
  284. } else {
  285. acc[key] = val === undefined || val === null ? "" : val; // 文字字段空值显示空字符串
  286. }
  287. return acc;
  288. }, {} as any);
  289. return {
  290. ...processedRow,
  291. displayIndex: start + idx + 1,
  292. };
  293. });
  294. });
  295. // 获取表格数据:仅数字字段处理NaN为0
  296. const fetchTable = async () => {
  297. console.log("正在获取表格数据...");
  298. try {
  299. loading.value = true;
  300. const response = await table({ table: currentTableName });
  301. console.log("获取到的数据:", response);
  302. // 适配后端返回的直接数组格式
  303. const rawData = Array.isArray(response.data) ? response.data : [];
  304. // 预处理数据:区分字段类型处理
  305. tableData.value = rawData.map((row: any) =>
  306. Object.entries(row).reduce((acc: any, [key, val]) => {
  307. if (isNumberField(key)) {
  308. acc[key] =
  309. (typeof val === "number" && isNaN(val)) || val === undefined
  310. ? 0
  311. : val;
  312. } else {
  313. acc[key] = val === undefined || val === null ? "" : val; // 文字字段空值存空字符串
  314. }
  315. return acc;
  316. }, {})
  317. );
  318. } catch (error) {
  319. console.error("获取数据时出错:", error);
  320. ElMessage.error("获取数据失败,请检查网络连接或服务器状态");
  321. } finally {
  322. loading.value = false;
  323. }
  324. };
  325. onMounted(() => {
  326. fetchTable();
  327. });
  328. // 表格行点击事件:文字字段保留原值,数字字段处理NaN为0
  329. const handleRowClick = (row: any) => {
  330. console.log("点击行数据:", row);
  331. selectedRow.value = row;
  332. Object.assign(
  333. formData,
  334. Object.entries(row).reduce((acc: any, [key, val]) => {
  335. if (isNumberField(key)) {
  336. acc[key] =
  337. (typeof val === "number" && isNaN(val)) || val === undefined
  338. ? 0
  339. : val;
  340. } else {
  341. acc[key] = val === undefined || val === null ? "" : val; // 文字字段空值显示空字符串
  342. }
  343. return acc;
  344. }, {})
  345. );
  346. };
  347. // 打开新增/编辑对话框:文字字段置空,数字字段可选置0
  348. const openDialog = (mode: "add" | "edit", row?: any) => {
  349. dialogMode.value = mode;
  350. dialogVisible.value = true;
  351. if (mode === "add") {
  352. selectedRow.value = null;
  353. // 新增时:文字字段置空,数字字段可选置空(根据业务调整)
  354. editableColumns.forEach((col) => {
  355. formData[col.dataKey] = isTextField(col.dataKey) ? "" : "";
  356. });
  357. } else if (row) {
  358. selectedRow.value = row;
  359. // 编辑时:区分字段类型赋值
  360. Object.assign(
  361. formData,
  362. Object.entries(row).reduce((acc: any, [key, val]) => {
  363. if (isNumberField(key)) {
  364. acc[key] =
  365. (typeof val === "number" && isNaN(val)) || val === undefined
  366. ? 0
  367. : val;
  368. } else {
  369. acc[key] = val === undefined || val === null ? "" : val;
  370. }
  371. return acc;
  372. }, {})
  373. );
  374. }
  375. };
  376. // 表单数据预处理:文字字段空字符串转为undefined(适配后端)
  377. function prepareFormData(
  378. formData: { [x: string]: any },
  379. excludeKeys = ["displayIndex"]
  380. ): { [x: string]: any } {
  381. const result: { [x: string]: any } = {};
  382. for (const key in formData) {
  383. if (!excludeKeys.includes(key)) {
  384. let value = formData[key];
  385. // 文字字段:空字符串转为undefined(后端可能需要NULL);数字字段:保留原值
  386. if (isTextField(key)) {
  387. result[key] = value === "" ? undefined : value;
  388. } else {
  389. result[key] = value === "" ? undefined : value; // 数字字段空值也转为undefined
  390. }
  391. }
  392. }
  393. return result;
  394. }
  395. // 表单提交:文字字段必填校验
  396. const submitForm = async () => {
  397. // 表单校验:文字字段必填
  398. if (!formRef.value) return;
  399. try {
  400. const isValid = await formRef.value.validate();
  401. if (!isValid) return;
  402. } catch (error) {
  403. console.error("表单验证失败:", error);
  404. return;
  405. }
  406. try {
  407. const dataToSubmit = prepareFormData(formData);
  408. let response;
  409. if (dialogMode.value === "add") {
  410. response = await addItem({
  411. table: currentTableName,
  412. item: dataToSubmit,
  413. });
  414. // 添加成功后,直接将新数据添加到本地数组
  415. if (response.data && response.data.inserted) {
  416. tableData.value.push(response.data.inserted);
  417. }
  418. } else {
  419. // 更详细的错误检查和日志
  420. console.log("编辑模式 - selectedRow:", selectedRow.value);
  421. if (!selectedRow.value) {
  422. ElMessage.error("未选中任何记录,请先选择要编辑的记录");
  423. return;
  424. }
  425. // 检查记录是否有 ID 字段
  426. if (!selectedRow.value.hasOwnProperty('id')) {
  427. console.error("记录缺少ID字段:", selectedRow.value);
  428. ElMessage.error("记录格式错误,缺少ID字段");
  429. return;
  430. }
  431. if (!selectedRow.value.id) {
  432. console.error("记录ID为空:", selectedRow.value);
  433. ElMessage.error("无法找到记录ID,请联系管理员");
  434. return;
  435. }
  436. response = await updateItem({
  437. table: currentTableName,
  438. id: selectedRow.value.id,
  439. update_data: dataToSubmit,
  440. });
  441. // 更新成功后,更新本地数据
  442. const index = tableData.value.findIndex(item => item.id === selectedRow.value.id);
  443. if (index > -1) {
  444. tableData.value[index] = {...tableData.value[index], ...dataToSubmit};
  445. } else {
  446. // 如果本地找不到记录,重新获取数据
  447. console.warn("本地未找到对应记录,重新获取数据");
  448. fetchTable();
  449. }
  450. }
  451. dialogVisible.value = false;
  452. ElMessage.success(dialogMode.value === "add" ? "添加成功" : "修改成功");
  453. } catch (error) {
  454. console.error("提交表单时发生错误:", error);
  455. let errorMessage = "未知错误";
  456. // 更详细的错误信息提取
  457. if (error && typeof error === "object") {
  458. const err = error as any;
  459. if (err.response && err.response.data) {
  460. errorMessage = err.response.data.detail || err.response.data.message || JSON.stringify(err.response.data);
  461. } else if (err.message) {
  462. errorMessage = err.message;
  463. }
  464. }
  465. ElMessage.error(`提交失败: ${errorMessage}`);
  466. }
  467. };
  468. // 表单验证:文字字段必填,数字字段可选(根据业务调整)
  469. function validateFormData(data: { [x: string]: any }) {
  470. return editableColumns.every((col) => {
  471. const value = data[col.dataKey];
  472. // 文字字段:必填(非空字符串);数字字段:可选(允许0或空)
  473. if (isTextField(col.dataKey)) {
  474. return value !== "" && value !== undefined && value !== null;
  475. } else {
  476. return true; // 数字字段可选,空值会被后端处理为NULL
  477. }
  478. });
  479. }
  480. // 删除记录
  481. const deleteItem = async (row: any) => {
  482. if (!row) {
  483. ElMessage.warning("请先选择一行记录");
  484. return;
  485. }
  486. try {
  487. await deleteItemApi({
  488. table: currentTableName,
  489. id: row.id,
  490. });
  491. // 直接从本地数据中删除,避免重新获取全部数据
  492. const index = tableData.value.findIndex((item) => item.id === row.id);
  493. if (index > -1) {
  494. tableData.value.splice(index, 1);
  495. }
  496. ElMessage.success("记录删除成功");
  497. } catch (error) {
  498. console.error("删除记录时发生错误:", error);
  499. ElMessage.error("删除失败,请重试");
  500. }
  501. };
  502. // 下载模板
  503. const downloadTemplateAction = async () => {
  504. try {
  505. await downloadTemplate(currentTableName);
  506. ElMessage.success("模板下载成功");
  507. } catch (error) {
  508. console.error("下载模板时发生错误:", error);
  509. ElMessage.error("下载模板失败,请重试");
  510. }
  511. };
  512. // 导出数据
  513. const exportDataAction = async () => {
  514. try {
  515. await exportData(currentTableName);
  516. ElMessage.success("数据导出成功");
  517. } catch (error) {
  518. console.error("导出数据时发生错误:", error);
  519. ElMessage.error("导出数据失败,请重试");
  520. }
  521. };
  522. // 处理文件选择(导入数据)
  523. const handleFileSelect = async (uploadFile: any) => {
  524. const file = uploadFile.raw;
  525. if (!file) {
  526. ElMessage.warning("请选择有效的 .xlsx 或 .csv 文件");
  527. return;
  528. }
  529. await importDataAction(file);
  530. };
  531. // 导入数据:区分字段类型处理
  532. const importDataAction = async (file: File) => {
  533. try {
  534. const response = await importData(currentTableName, file);
  535. if (response.success) {
  536. const { total_data, new_data, duplicate_data } = response;
  537. ElMessage({
  538. message: `导入成功!共${total_data}条,新增${new_data}条,重复${duplicate_data}条`,
  539. type: "success",
  540. duration: 3000,
  541. });
  542. fetchTable(); // 刷新表格,应用字段类型处理逻辑
  543. }
  544. } catch (error) {
  545. let errorMessage = "数据导入失败";
  546. if (
  547. error &&
  548. typeof error === "object" &&
  549. "message" in error &&
  550. "response" in error &&
  551. typeof (error as any).response === "object"
  552. ) {
  553. // 适配后端返回的重复记录详情
  554. if ((error as any).response?.data?.detail) {
  555. const detail = (error as any).response.data.detail;
  556. if (detail.duplicates) {
  557. // 显示重复记录的行号和文字字段内容
  558. const duplicateMsg = detail.duplicates
  559. .map(
  560. (item: any) =>
  561. `第${item.row_number}行(${Object.entries(item.duplicate_fields)
  562. .map(([k, v]) => `${k}=${v || "空"}`)
  563. .join(",")}`
  564. )
  565. .join(";");
  566. errorMessage = `导入失败:${detail.message},重复记录:${duplicateMsg}`;
  567. } else {
  568. errorMessage = `导入失败:${detail.message || detail}`;
  569. }
  570. } else {
  571. errorMessage += `: ${(error as unknown as Error).message}`;
  572. }
  573. }
  574. ElMessage.error({
  575. message: errorMessage,
  576. duration: 5000, // 长一点的提示时间,让用户看清重复详情
  577. });
  578. }
  579. };
  580. // 分页大小改变
  581. const handleSizeChange = (val: number) => {
  582. pageSize4.value = val;
  583. currentPage4.value = 1;
  584. };
  585. // 当前页改变
  586. const handleCurrentChange = (val: number) => {
  587. currentPage4.value = val;
  588. };
  589. // 表格值格式化:仅数字字段保留3位小数,文字字段显示原始内容
  590. const formatTableValue = (row: any, column: any, cellValue: any) => {
  591. const dataKey = column.prop;
  592. // 文字字段:空值显示空字符串,非空显示原始文字
  593. if (isTextField(dataKey)) {
  594. return cellValue === undefined || cellValue === null ? "" : cellValue;
  595. }
  596. // 数字字段:NaN/undefined显示0,保留3位小数
  597. if (isNumberField(dataKey)) {
  598. if (isNaN(cellValue) || cellValue === undefined || cellValue === null) {
  599. return 0;
  600. }
  601. return typeof cellValue === "number" ? cellValue.toFixed(3) : cellValue;
  602. }
  603. // 其他字段:默认显示
  604. return cellValue;
  605. };
  606. // 对话框标题
  607. const dialogTitle = computed(() => {
  608. return dialogMode.value === "add"
  609. ? `新增记录`
  610. : "编辑记录";
  611. });
  612. // 对话框提交按钮文本
  613. const dialogSubmitButtonText = computed(() => {
  614. return dialogMode.value === "add" ? "添加" : "保存";
  615. });
  616. </script>
  617. <style scoped>
  618. .app-container {
  619. padding: 20px 40px;
  620. min-height: 100vh;
  621. background-color: #f0f2f5;
  622. box-sizing: border-box;
  623. display: flex;
  624. flex-direction: column;
  625. align-items: center;
  626. }
  627. .button-group {
  628. display: flex;
  629. gap: 12px;
  630. justify-content: flex-end;
  631. width: 100%;
  632. margin-bottom: 20px;
  633. }
  634. .custom-button {
  635. color: #fff;
  636. border: none;
  637. border-radius: 8px;
  638. font-size: 14px;
  639. padding: 10px 18px;
  640. transition: transform 0.3s ease, background-color 0.3s ease;
  641. min-width: 130px;
  642. display: flex;
  643. align-items: center;
  644. justify-content: center;
  645. }
  646. .download-button,
  647. .export-button,
  648. .add-button,
  649. .import-button {
  650. background-color: #67c23a;
  651. }
  652. .download-button:hover,
  653. .export-button:hover,
  654. .import-button:hover,
  655. .add-button:hover {
  656. background-color: #85ce61;
  657. transform: scale(1.05);
  658. }
  659. .custom-table {
  660. width: 100%;
  661. border-radius: 8px;
  662. overflow: hidden;
  663. box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
  664. background-color: #fff;
  665. margin-top: 10px;
  666. }
  667. :deep(.el-table th) {
  668. background: linear-gradient(180deg, #61e054, #4db944);
  669. color: #fff;
  670. font-weight: bold;
  671. text-align: center;
  672. padding: 14px 0;
  673. font-size: 14px;
  674. }
  675. :deep(.el-table__row:nth-child(odd)) {
  676. background-color: #e4fbe5;
  677. }
  678. :deep(.el-table__row:nth-child(even)) {
  679. background-color: #ffffff;
  680. }
  681. :deep(.el-table td) {
  682. padding: 12px 10px;
  683. text-align: center;
  684. border-bottom: 1px solid #ebeef5;
  685. }
  686. .action-button {
  687. font-size: 16px;
  688. margin-right: 5px;
  689. border-radius: 50%;
  690. transition: transform 0.3s ease, background-color 0.3s ease;
  691. }
  692. .edit-button {
  693. background-color: #409eff;
  694. color: #fff;
  695. }
  696. .edit-button:hover {
  697. background-color: #66b1ff;
  698. transform: scale(1.1);
  699. }
  700. .delete-button {
  701. background-color: #f56c6c;
  702. color: #fff;
  703. }
  704. .delete-button:hover {
  705. background-color: #f78989;
  706. transform: scale(1.1);
  707. }
  708. .el-form-item__label {
  709. width: 150px;
  710. font-weight: bold;
  711. }
  712. .el-form-item__content {
  713. margin-left: 150px;
  714. }
  715. .pagination-container {
  716. margin-top: 30px;
  717. text-align: center;
  718. }
  719. .action-buttons {
  720. display: flex;
  721. justify-content: center;
  722. }
  723. /* ================= 弹框样式优化 ================= */
  724. :deep(.el-dialog) {
  725. border-radius: 16px !important;
  726. overflow: hidden;
  727. box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15) !important;
  728. background: linear-gradient(145deg, #ffffff, #f5f9ff);
  729. border: 1px solid #e0e7ff;
  730. }
  731. /* 弹框头部样式 */
  732. .dialog-header {
  733. position: relative;
  734. padding: 20px 24px 10px;
  735. text-align: center;
  736. background: linear-gradient(90deg, #4db944, #61e054);
  737. }
  738. .dialog-header h2 {
  739. margin: 0;
  740. font-size: 22px;
  741. font-weight: 600;
  742. color: #fff;
  743. text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
  744. }
  745. .header-decoration {
  746. height: 4px;
  747. background: linear-gradient(90deg, #3a9a32, #4fc747);
  748. border-radius: 2px;
  749. margin-top: 12px;
  750. width: 60%;
  751. margin-left: auto;
  752. margin-right: auto;
  753. }
  754. /* 表单容器样式 */
  755. .custom-dialog-form {
  756. padding: 25px 40px 15px;
  757. }
  758. /* 表单项样式优化 */
  759. .custom-form-item {
  760. margin-bottom: 22px !important;
  761. }
  762. :deep(.el-form-item__label) {
  763. display: block;
  764. text-align: left;
  765. margin-bottom: 8px !important;
  766. font-size: 15px;
  767. font-weight: 600;
  768. color: #4a5568;
  769. padding: 0 !important;
  770. line-height: 1.5;
  771. }
  772. /* 输入框样式优化 */
  773. :deep(.custom-dialog-input .el-input__inner) {
  774. height: 44px !important;
  775. padding: 0 16px !important;
  776. font-size: 15px;
  777. border: 1px solid #cbd5e0 !important;
  778. border-radius: 10px !important;
  779. background-color: #f8fafc;
  780. box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05);
  781. transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
  782. }
  783. :deep(.custom-dialog-input .el-input__inner:hover) {
  784. border-color: #a0aec0 !important;
  785. }
  786. :deep(.custom-dialog-input .el-input__inner:focus) {
  787. border-color: #4db944 !important;
  788. box-shadow: 0 0 0 3px rgba(77, 185, 68, 0.15) !important;
  789. background-color: #fff;
  790. }
  791. /* 弹框底部按钮容器 */
  792. .dialog-footer {
  793. display: flex;
  794. gap: 16px;
  795. justify-content: center;
  796. padding: 15px 40px 25px;
  797. }
  798. /* 按钮基础样式 */
  799. .custom-cancel-button,
  800. .custom-submit-button {
  801. min-width: 120px;
  802. height: 44px;
  803. border-radius: 10px;
  804. font-size: 16px;
  805. font-weight: 500;
  806. transition: all 0.3s ease;
  807. letter-spacing: 0.5px;
  808. border: none;
  809. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
  810. }
  811. /* 取消按钮样式优化 */
  812. .custom-cancel-button {
  813. background: linear-gradient(145deg, #f7fafc, #edf2f7);
  814. color: #4a5568;
  815. }
  816. .custom-cancel-button:hover {
  817. background: linear-gradient(145deg, #e2e8f0, #cbd5e0);
  818. transform: translateY(-2px);
  819. box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
  820. }
  821. /* 提交按钮样式优化 */
  822. .custom-submit-button {
  823. background: linear-gradient(145deg, #4db944, #61e054);
  824. color: white;
  825. position: relative;
  826. overflow: hidden;
  827. }
  828. .custom-submit-button::before {
  829. content: '';
  830. position: absolute;
  831. top: 0;
  832. left: -100%;
  833. width: 100%;
  834. height: 100%;
  835. background: linear-gradient(
  836. 90deg,
  837. transparent,
  838. rgba(255, 255, 255, 0.3),
  839. transparent
  840. );
  841. transition: 0.5s;
  842. }
  843. .custom-submit-button:hover::before {
  844. left: 100%;
  845. }
  846. .custom-submit-button:hover {
  847. transform: translateY(-2px);
  848. box-shadow: 0 6px 15px rgba(77, 185, 68, 0.4);
  849. }
  850. /* 彻底隐藏文件信息相关元素 */
  851. :deep(.import-upload .el-upload-list) {
  852. display: none !important;
  853. }
  854. </style>