77681 13 часов назад
Родитель
Сommit
91e7f24050
49 измененных файлов с 1716 добавлено и 483 удалено
  1. 1 1
      gw-slgcrun/src/main/java/com/goldenwater/slgc/controller/CmsArticleController.java
  2. 43 1
      gw-slgcrun/src/main/java/com/goldenwater/slgc/controller/DikeController.java
  3. 43 0
      gw-slgcrun/src/main/java/com/goldenwater/slgc/controller/PustController.java
  4. 44 0
      gw-slgcrun/src/main/java/com/goldenwater/slgc/controller/ReservoirController.java
  5. 43 0
      gw-slgcrun/src/main/java/com/goldenwater/slgc/controller/WagaController.java
  6. 33 0
      gw-slgcrun/src/main/java/com/goldenwater/slgc/domain/AttDikeBase.java
  7. 23 0
      gw-slgcrun/src/main/java/com/goldenwater/slgc/domain/AttPustBase.java
  8. 33 0
      gw-slgcrun/src/main/java/com/goldenwater/slgc/domain/AttResBase.java
  9. 24 0
      gw-slgcrun/src/main/java/com/goldenwater/slgc/domain/AttWagaBase.java
  10. 5 3
      gw-slgcrun/src/main/java/com/goldenwater/slgc/domain/CmsArticle.java
  11. 8 0
      gw-slgcrun/src/main/java/com/goldenwater/slgc/service/DikeService.java
  12. 8 0
      gw-slgcrun/src/main/java/com/goldenwater/slgc/service/PustService.java
  13. 8 0
      gw-slgcrun/src/main/java/com/goldenwater/slgc/service/ReservoirService.java
  14. 8 0
      gw-slgcrun/src/main/java/com/goldenwater/slgc/service/WagaService.java
  15. 40 1
      gw-slgcrun/src/main/java/com/goldenwater/slgc/service/impl/CmsArticleServiceImpl.java
  16. 51 0
      gw-slgcrun/src/main/java/com/goldenwater/slgc/service/impl/DikeServiceImpl.java
  17. 51 0
      gw-slgcrun/src/main/java/com/goldenwater/slgc/service/impl/PustServiceImpl.java
  18. 51 0
      gw-slgcrun/src/main/java/com/goldenwater/slgc/service/impl/ReservoirServiceImpl.java
  19. 51 0
      gw-slgcrun/src/main/java/com/goldenwater/slgc/service/impl/WagaServiceImpl.java
  20. 5 2
      gw-slgcrun/src/main/resources/mapper/slgc/AttDikeBaseMapper.xml
  21. 13 4
      gw-slgcrun/src/main/resources/mapper/slgc/AttPustBaseMapper.xml
  22. 13 4
      gw-slgcrun/src/main/resources/mapper/slgc/AttResBaseMapper.xml
  23. 13 4
      gw-slgcrun/src/main/resources/mapper/slgc/AttWagaBaseMapper.xml
  24. 4 1
      gw-slgcrun/src/main/resources/mapper/slgc/CmsArticleMapper.xml
  25. 4 0
      gw-ui/src/api/slgc/dike/index.js
  26. 1 0
      gw-ui/src/api/slgc/doc/index.js
  27. 4 0
      gw-ui/src/api/slgc/pust/index.js
  28. 4 0
      gw-ui/src/api/slgc/reservoir/index.js
  29. 4 0
      gw-ui/src/api/slgc/waga/index.js
  30. 76 53
      gw-ui/src/components/ExcelImportDialog/index.vue
  31. 1 0
      gw-ui/src/views/monitor/operlog/detail.vue
  32. 1 0
      gw-ui/src/views/slgc/cms/article/detail.vue
  33. 56 77
      gw-ui/src/views/slgc/cms/article/index.vue
  34. 1 0
      gw-ui/src/views/slgc/cms/category/index.vue
  35. 171 71
      gw-ui/src/views/slgc/dike/index.vue
  36. 99 32
      gw-ui/src/views/slgc/doc/index.vue
  37. 1 0
      gw-ui/src/views/slgc/earlywarning/index.vue
  38. 22 4
      gw-ui/src/views/slgc/law/index.vue
  39. 142 73
      gw-ui/src/views/slgc/pust/index.vue
  40. 189 29
      gw-ui/src/views/slgc/reservoir/index.vue
  41. 1 0
      gw-ui/src/views/slgc/sw/index.vue
  42. 63 16
      gw-ui/src/views/slgc/unit/file.vue
  43. 55 12
      gw-ui/src/views/slgc/unit/index.vue
  44. 139 81
      gw-ui/src/views/slgc/waga/index.vue
  45. 1 0
      gw-ui/src/views/slgc/waterFunction/index.vue
  46. 1 0
      gw-ui/src/views/slgc/wco/km/index.vue
  47. 62 14
      gw-ui/src/views/slgc/zggc/file/index.vue
  48. 1 0
      gw-ui/src/views/slgc/zggc/portal.vue
  49. 1 0
      gw-ui/src/views/slgc/zggc/video/index.vue

+ 1 - 1
gw-slgcrun/src/main/java/com/goldenwater/slgc/controller/CmsArticleController.java

@@ -168,7 +168,7 @@ public class CmsArticleController extends BaseController {
     /**
      * 导入内容文章列表
      *
-     * @param file 上传的Excel文件
+     * @param file 上传的Excel文件   
      * @param cateId 目标分类ID(可选,导入时统一归类)
      * @return 导入结果
      */

+ 43 - 1
gw-slgcrun/src/main/java/com/goldenwater/slgc/controller/DikeController.java

@@ -16,11 +16,13 @@ import com.goldenwater.common.core.controller.BaseController;
 import com.goldenwater.common.core.domain.AjaxResult;
 import com.goldenwater.common.core.page.TableDataInfo;
 import com.goldenwater.common.enums.BusinessType;
+import com.goldenwater.common.utils.poi.ExcelUtil;
 import com.goldenwater.slgc.domain.AttDikeBase;
 import com.goldenwater.slgc.service.DikeService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
-import org.springframework.security.access.prepost.PreAuthorize;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.web.multipart.MultipartFile;
 
 /**
  * 堤防信息管理
@@ -110,4 +112,44 @@ public class DikeController extends BaseController {
         List<Map<String, Object>> list = dikeService.countType();
         return success(list);
     }
+
+    /**
+     * 导出堤防信息列表
+     *
+     * @param attDikeBase 查询条件
+     */
+    @Operation(summary = "01.07 导出堤防信息")
+    @Log(title = "堤防管理", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public void export(AttDikeBase attDikeBase, HttpServletResponse response) {
+        List<AttDikeBase> list = dikeService.selectAttDikeBaseList(attDikeBase);
+        ExcelUtil<AttDikeBase> util = new ExcelUtil<AttDikeBase>(AttDikeBase.class);
+        util.exportExcel(response, list, "堤防信息数据");
+    }
+
+    /**
+     * 导入堤防信息列表
+     *
+     * @param file 上传的Excel文件
+     * @return 导入结果
+     */
+    @Operation(summary = "01.08 导入堤防信息")
+    @Log(title = "堤防管理", businessType = BusinessType.IMPORT)
+    @PostMapping("/importData")
+    public AjaxResult importData(MultipartFile file) throws Exception {
+        ExcelUtil<AttDikeBase> util = new ExcelUtil<AttDikeBase>(AttDikeBase.class);
+        List<AttDikeBase> list = util.importExcel(file.getInputStream());
+        String message = dikeService.importDike(list);
+        return success(message);
+    }
+
+    /**
+     * 下载导入模板
+     */
+    @Operation(summary = "01.09 下载堤防导入模板")
+    @GetMapping("/importTemplate")
+    public void importTemplate(HttpServletResponse response) {
+        ExcelUtil<AttDikeBase> util = new ExcelUtil<AttDikeBase>(AttDikeBase.class);
+        util.importTemplateExcel(response, "堤防信息数据");
+    }
 }

+ 43 - 0
gw-slgcrun/src/main/java/com/goldenwater/slgc/controller/PustController.java

@@ -16,10 +16,13 @@ import com.goldenwater.common.core.controller.BaseController;
 import com.goldenwater.common.core.domain.AjaxResult;
 import com.goldenwater.common.core.page.TableDataInfo;
 import com.goldenwater.common.enums.BusinessType;
+import com.goldenwater.common.utils.poi.ExcelUtil;
 import com.goldenwater.slgc.domain.AttPustBase;
 import com.goldenwater.slgc.service.PustService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.web.multipart.MultipartFile;
 
 /**
  * 水闸信息管理
@@ -109,4 +112,44 @@ public class PustController extends BaseController {
         List<Map<String, Object>> list = pustService.countType();
         return success(list);
     }
+
+    /**
+     * 导出水闸信息列表
+     *
+     * @param attPustBase 查询条件
+     */
+    @Operation(summary = "03.07 导出水闸信息")
+    @Log(title = "水闸管理", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public void export(AttPustBase attPustBase, HttpServletResponse response) {
+        List<AttPustBase> list = pustService.selectAttPustBaseList(attPustBase);
+        ExcelUtil<AttPustBase> util = new ExcelUtil<AttPustBase>(AttPustBase.class);
+        util.exportExcel(response, list, "水闸信息数据");
+    }
+
+    /**
+     * 导入水闸信息列表
+     *
+     * @param file 上传的Excel文件
+     * @return 导入结果
+     */
+    @Operation(summary = "03.08 导入水闸信息")
+    @Log(title = "水闸管理", businessType = BusinessType.IMPORT)
+    @PostMapping("/importData")
+    public AjaxResult importData(MultipartFile file) throws Exception {
+        ExcelUtil<AttPustBase> util = new ExcelUtil<AttPustBase>(AttPustBase.class);
+        List<AttPustBase> list = util.importExcel(file.getInputStream());
+        String message = pustService.importPust(list);
+        return success(message);
+    }
+
+    /**
+     * 下载导入模板
+     */
+    @Operation(summary = "03.09 下载水闸导入模板")
+    @GetMapping("/importTemplate")
+    public void importTemplate(HttpServletResponse response) {
+        ExcelUtil<AttPustBase> util = new ExcelUtil<AttPustBase>(AttPustBase.class);
+        util.importTemplateExcel(response, "水闸信息数据");
+    }
 }

+ 44 - 0
gw-slgcrun/src/main/java/com/goldenwater/slgc/controller/ReservoirController.java

@@ -10,16 +10,20 @@ import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.PutMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 import com.goldenwater.common.annotation.Log;
 import com.goldenwater.common.core.controller.BaseController;
 import com.goldenwater.common.core.domain.AjaxResult;
 import com.goldenwater.common.core.page.TableDataInfo;
 import com.goldenwater.common.enums.BusinessType;
+import com.goldenwater.common.utils.poi.ExcelUtil;
 import com.goldenwater.slgc.domain.AttResBase;
 import com.goldenwater.slgc.service.ReservoirService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.web.multipart.MultipartFile;
 
 /**
  * 水库信息管理
@@ -109,4 +113,44 @@ public class ReservoirController extends BaseController {
         List<Map<String, Object>> list = reservoirService.countType();
         return success(list);
     }
+
+    /**
+     * 导出水库信息列表
+     *
+     * @param attResBase 查询条件
+     */
+    @Operation(summary = "02.07 导出水库信息")
+    @Log(title = "水库管理", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public void export(AttResBase attResBase, HttpServletResponse response) {
+        List<AttResBase> list = reservoirService.selectAttResBaseList(attResBase);
+        ExcelUtil<AttResBase> util = new ExcelUtil<AttResBase>(AttResBase.class);
+        util.exportExcel(response, list, "水库信息数据");
+    }
+
+    /**
+     * 导入水库信息列表
+     *
+     * @param file 上传的Excel文件
+     * @return 导入结果
+     */
+    @Operation(summary = "02.08 导入水库信息")
+    @Log(title = "水库管理", businessType = BusinessType.IMPORT)
+    @PostMapping("/importData")
+    public AjaxResult importData(MultipartFile file) throws Exception {
+        ExcelUtil<AttResBase> util = new ExcelUtil<AttResBase>(AttResBase.class);
+        List<AttResBase> list = util.importExcel(file.getInputStream());
+        String message = reservoirService.importReservoir(list);
+        return success(message);
+    }
+
+    /**
+     * 下载导入模板
+     */
+    @Operation(summary = "02.09 下载水库导入模板")
+    @GetMapping("/importTemplate")
+    public void importTemplate(HttpServletResponse response) {
+        ExcelUtil<AttResBase> util = new ExcelUtil<AttResBase>(AttResBase.class);
+        util.importTemplateExcel(response, "水库信息数据");
+    }
 }

+ 43 - 0
gw-slgcrun/src/main/java/com/goldenwater/slgc/controller/WagaController.java

@@ -16,10 +16,13 @@ import com.goldenwater.common.core.controller.BaseController;
 import com.goldenwater.common.core.domain.AjaxResult;
 import com.goldenwater.common.core.page.TableDataInfo;
 import com.goldenwater.common.enums.BusinessType;
+import com.goldenwater.common.utils.poi.ExcelUtil;
 import com.goldenwater.slgc.domain.AttWagaBase;
 import com.goldenwater.slgc.service.WagaService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.web.multipart.MultipartFile;
 
 /**
  * 泵站信息管理
@@ -109,4 +112,44 @@ public class WagaController extends BaseController {
         List<Map<String, Object>> list = wagaService.countType();
         return success(list);
     }
+
+    /**
+     * 导出泵站信息列表
+     *
+     * @param attWagaBase 查询条件
+     */
+    @Operation(summary = "04.07 导出泵站信息")
+    @Log(title = "泵站管理", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public void export(AttWagaBase attWagaBase, HttpServletResponse response) {
+        List<AttWagaBase> list = wagaService.selectAttWagaBaseList(attWagaBase);
+        ExcelUtil<AttWagaBase> util = new ExcelUtil<AttWagaBase>(AttWagaBase.class);
+        util.exportExcel(response, list, "泵站信息数据");
+    }
+
+    /**
+     * 导入泵站信息列表
+     *
+     * @param file 上传的Excel文件
+     * @return 导入结果
+     */
+    @Operation(summary = "04.08 导入泵站信息")
+    @Log(title = "泵站管理", businessType = BusinessType.IMPORT)
+    @PostMapping("/importData")
+    public AjaxResult importData(MultipartFile file) throws Exception {
+        ExcelUtil<AttWagaBase> util = new ExcelUtil<AttWagaBase>(AttWagaBase.class);
+        List<AttWagaBase> list = util.importExcel(file.getInputStream());
+        String message = wagaService.importWaga(list);
+        return success(message);
+    }
+
+    /**
+     * 下载导入模板
+     */
+    @Operation(summary = "04.09 下载泵站导入模板")
+    @GetMapping("/importTemplate")
+    public void importTemplate(HttpServletResponse response) {
+        ExcelUtil<AttWagaBase> util = new ExcelUtil<AttWagaBase>(AttWagaBase.class);
+        util.importTemplateExcel(response, "泵站信息数据");
+    }
 }

+ 33 - 0
gw-slgcrun/src/main/java/com/goldenwater/slgc/domain/AttDikeBase.java

@@ -1,6 +1,8 @@
 package com.goldenwater.slgc.domain;
 
+import com.goldenwater.common.annotation.Excel;
 import com.goldenwater.common.core.domain.BaseEntity;
+import com.fasterxml.jackson.annotation.JsonFormat;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.AllArgsConstructor;
 import lombok.Data;
@@ -19,62 +21,93 @@ public class AttDikeBase extends BaseEntity {
     private static final long serialVersionUID = 1L;
 
     @Schema(description = "堤防编码")
+    @Excel(name = "堤防代码", sort = 1, width = 20)
     private String dikeCode;
     @Schema(description = "采集日期")
+    @Excel(name = "属性采集时间", sort = 22, dateFormat = "yyyy-MM-dd", type = Excel.Type.EXPORT)
     private Date collDate;
     @Schema(description = "堤防名称")
+    @Excel(name = "堤防名称", sort = 2, width = 20)
     private String dikeName;
     @Schema(description = "起点经度")
+    @Excel(name = "起点经度", sort = 23, cellType = Excel.ColumnType.NUMERIC)
     private BigDecimal startLong;
     @Schema(description = "起点纬度")
+    @Excel(name = "起点纬度", sort = 24, cellType = Excel.ColumnType.NUMERIC)
     private BigDecimal startLat;
     @Schema(description = "终点经度")
+    @Excel(name = "终点经度", sort = 25, cellType = Excel.ColumnType.NUMERIC)
     private BigDecimal endLong;
     @Schema(description = "终点纬度")
+    @Excel(name = "终点纬度", sort = 26, cellType = Excel.ColumnType.NUMERIC)
     private BigDecimal endLat;
     @Schema(description = "起点位置")
+    @Excel(name = "起点所在位置", sort = 4, width = 30)
     private String startLoc;
     @Schema(description = "终点位置")
+    @Excel(name = "终点所在位置", sort = 5, width = 30)
     private String endLoc;
     @Schema(description = "堤防等级")
+    @Excel(name = "堤防级别", sort = 3, readConverterExp = "1=一级,2=二级,3=三级,4=四级,5=五级", combo = {"一级","二级","三级","四级","五级"})
     private String dikeGrad;
     @Schema(description = "堤防类型")
+    @Excel(name = "堤防类型", sort = 6, readConverterExp = "1=江河堤防,2=湖堤,3=海堤,4=围堤,9=其他", combo = {"江河堤防","湖堤","海堤","围堤","其他"})
     private String dikeType;
     @Schema(description = "堤防结构")
+    @Excel(name = "堤防型式", sort = 7, readConverterExp = "1=土堤,2=砌石堤,3=土石混合堤,4=钢筋混凝土防洪墙,9=其他", combo = {"土堤","砌石堤","土石混合堤","钢筋混凝土防洪墙","其他"})
     private String dikePatt;
     @Schema(description = "堤防长度")
+    @Excel(name = "堤防长度", sort = 8, suffix = " m")
     private BigDecimal dikeLen;
     @Schema(description = "起始桩号")
+    @Excel(name = "堤防起点桩号", sort = 9)
     private String dikeStartNum;
     @Schema(description = "终止桩号")
+    @Excel(name = "堤防终点桩号", sort = 10)
     private String dikeEndNum;
     @Schema(description = "起点堤顶高程")
+    @Excel(name = "起点堤顶高程", sort = 11, suffix = " m")
     private BigDecimal startDikeTopEl;
     @Schema(description = "终点堤顶高程")
+    @Excel(name = "终点堤顶高程", sort = 12, suffix = " m")
     private BigDecimal endDikeTopEl;
     @Schema(description = "最小堤高")
+    @Excel(name = "堤防最小高度", sort = 13, suffix = " m")
     private BigDecimal dikeHeigMin;
     @Schema(description = "最大堤高")
+    @Excel(name = "堤防最大高度", sort = 14, suffix = " m")
     private BigDecimal dikeHeigMax;
     @Schema(description = "最小堤顶宽")
+    @Excel(name = "堤顶最小宽度", sort = 15, suffix = " m")
     private BigDecimal dikeTopWidMin;
     @Schema(description = "最大堤顶宽")
+    @Excel(name = "堤顶最大宽度", sort = 16, suffix = " m")
     private BigDecimal dikeTopWidMax;
     @Schema(description = "工程状态")
+    @Excel(name = "工程建设情况", sort = 17, readConverterExp = "1=已建,2=在建,3=规划", combo = {"已建","在建","规划"})
     private String engStat;
     @Schema(description = "运行状态")
+    @Excel(name = "运行状况", sort = 21, readConverterExp = "1=在用良好,2=在用故障,3=停用", combo = {"在用良好","在用故障","停用"})
     private String runStat;
     @Schema(description = "工程任务")
+    @Excel(name = "工程任务", sort = 18)
     private String engTask;
     @Schema(description = "开工日期")
+    @Excel(name = "开工时间", sort = 19, dateFormat = "yyyy-MM-dd")
+    @JsonFormat(pattern = "yyyy-MM-dd")
     private Date startDate;
     @Schema(description = "竣工日期")
+    @Excel(name = "建成时间", sort = 20, dateFormat = "yyyy-MM-dd")
+    @JsonFormat(pattern = "yyyy-MM-dd")
     private Date compDate;
     @Schema(description = "管理单位")
+    @Excel(name = "归口管理部门", sort = 27, width = 20)
     private String admDep;
     @Schema(description = "备注")
+    @Excel(name = "备注", sort = 28, width = 20)
     private String note;
     @Schema(description = "更新日期")
+    @Excel(name = "属性更新时间", sort = 29, dateFormat = "yyyy-MM-dd", type = Excel.Type.EXPORT)
     private Date updDate;
     @Schema(description = "数据来源")
     private String source;

+ 23 - 0
gw-slgcrun/src/main/java/com/goldenwater/slgc/domain/AttPustBase.java

@@ -1,6 +1,8 @@
 package com.goldenwater.slgc.domain;
 
+import com.goldenwater.common.annotation.Excel;
 import com.goldenwater.common.core.domain.BaseEntity;
+import com.fasterxml.jackson.annotation.JsonFormat;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.AllArgsConstructor;
 import lombok.Data;
@@ -19,54 +21,75 @@ public class AttPustBase extends BaseEntity {
     private static final long serialVersionUID = 1L;
 
     @Schema(description = "水闸编码")
+    @Excel(name = "水闸代码", sort = 1, width = 20)
     private String pustCode;
     @Schema(description = "采集日期")
     private Date collDate;
     @Schema(description = "水闸名称")
+    @Excel(name = "水闸名称", sort = 2, width = 20)
     private String pustName;
     @Schema(description = "水闸经度")
+    @Excel(name = "起点经度", sort = 3, cellType = Excel.ColumnType.NUMERIC)
     private BigDecimal pustLong;
     @Schema(description = "水闸纬度")
+    @Excel(name = "起点纬度", sort = 4, cellType = Excel.ColumnType.NUMERIC)
     private BigDecimal pustLat;
     @Schema(description = "水闸位置")
+    @Excel(name = "水闸所在位置", sort = 7, width = 30)
     private String pustLoc;
     @Schema(description = "水闸类型")
+    @Excel(name = "水闸类型", sort = 8, readConverterExp = "1=分(泄)洪闸,2=节制闸,3=排(退)水闸,4=引(进)水闸,5=挡潮闸,6=船闸,9=其他", combo = {"分(泄)洪闸","节制闸","排(退)水闸","引(进)水闸","挡潮闸","船闸","其他"})
     private String pustType;
     @Schema(description = "设计流量")
+    @Excel(name = "最大过闸流量", sort = 11)
     private BigDecimal insFlow;
     @Schema(description = "装机功率")
+    @Excel(name = "装机功率", sort = 18)
     private BigDecimal insPow;
     @Schema(description = "水泵数量")
+    @Excel(name = "闸孔数量", sort = 12)
     private BigDecimal pumpNum;
     @Schema(description = "机组台数")
     private BigDecimal pumpSetNum;
     @Schema(description = "总装机容量")
     private BigDecimal desTotInsCap;
     @Schema(description = "设计扬程")
+    @Excel(name = "设计扬程", sort = 5)
     private BigDecimal desHead;
     @Schema(description = "工程任务")
+    @Excel(name = "工程任务", sort = 6)
     private String engTask;
     @Schema(description = "灌溉范围")
     private String wasuRang;
     @Schema(description = "工程等级")
+    @Excel(name = "工程等别", sort = 9, readConverterExp = "1=Ⅰ等,2=Ⅱ等,3=Ⅲ等,4=Ⅳ等,5=Ⅴ等", combo = {"Ⅰ等","Ⅱ等","Ⅲ等","Ⅳ等","Ⅴ等"})
     private String engGrad;
     @Schema(description = "工程规模")
+    @Excel(name = "工程规模", sort = 10, readConverterExp = "1=大(1)型,2=大(2)型,3=中型,4=小(1)型,5=小(2)型", combo = {"大(1)型","大(2)型","中型","小(1)型","小(2)型"})
     private String engScal;
     @Schema(description = "主要水工类型")
     private String wainWasoType;
     @Schema(description = "主要建筑物级别")
+    @Excel(name = "主要建筑物级别", sort = 19, readConverterExp = "1=1级,2=2级,3=3级,4=4级,5=5级", combo = {"1级","2级","3级","4级","5级"})
     private String mainBuildGrad;
     @Schema(description = "工程状态")
+    @Excel(name = "工程建设情况", sort = 13, readConverterExp = "1=已建,2=在建,3=规划", combo = {"已建","在建","规划"})
     private String engStat;
     @Schema(description = "开工日期")
+    @Excel(name = "开工时间", sort = 14, dateFormat = "yyyy-MM-dd")
+    @JsonFormat(pattern = "yyyy-MM-dd")
     private Date startDate;
     @Schema(description = "竣工日期")
+    @Excel(name = "建成时间", sort = 15, dateFormat = "yyyy-MM-dd")
+    @JsonFormat(pattern = "yyyy-MM-dd")
     private Date compDate;
     @Schema(description = "投产日期")
     private Date startRunDate;
     @Schema(description = "管理单位")
+    @Excel(name = "归口管理部门", sort = 16, width = 20)
     private String admDep;
     @Schema(description = "备注")
+    @Excel(name = "备注", sort = 17, width = 20)
     private String note;
     @Schema(description = "更新日期")
     private Date updDate;

+ 33 - 0
gw-slgcrun/src/main/java/com/goldenwater/slgc/domain/AttResBase.java

@@ -1,6 +1,8 @@
 package com.goldenwater.slgc.domain;
 
+import com.goldenwater.common.annotation.Excel;
 import com.goldenwater.common.core.domain.BaseEntity;
+import com.fasterxml.jackson.annotation.JsonFormat;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.AllArgsConstructor;
 import lombok.Data;
@@ -19,66 +21,97 @@ public class AttResBase extends BaseEntity {
     private static final long serialVersionUID = 1L;
 
     @Schema(description = "水库编码")
+    @Excel(name = "水库代码", sort = 1, width = 20)
     private String resCode;
     @Schema(description = "采集日期")
     private Date collDate;
     @Schema(description = "水库名称")
+    @Excel(name = "水库名称", sort = 2, width = 20)
     private String resName;
     @Schema(description = "左下经度")
+    @Excel(name = "左下角经度", sort = 3, cellType = Excel.ColumnType.NUMERIC)
     private BigDecimal lowLeftLong;
     @Schema(description = "左下纬度")
+    @Excel(name = "左下角纬度", sort = 4, cellType = Excel.ColumnType.NUMERIC)
     private BigDecimal lowLeftLat;
     @Schema(description = "右上经度")
+    @Excel(name = "右上角经度", sort = 5, cellType = Excel.ColumnType.NUMERIC)
     private BigDecimal upRightLong;
     @Schema(description = "右上纬度")
+    @Excel(name = "右上角纬度", sort = 6, cellType = Excel.ColumnType.NUMERIC)
     private BigDecimal upRightLat;
     @Schema(description = "水库位置")
+    @Excel(name = "水库所在位置", sort = 7, width = 30)
     private String resLoc;
     @Schema(description = "水库类型")
+    @Excel(name = "水库类型", sort = 8, readConverterExp = "1=山丘水库,2=平原水库,3=地下水库", combo = {"山丘水库","平原水库","地下水库"})
     private String resType;
     @Schema(description = "工程等级")
+    @Excel(name = "工程等别", sort = 9, readConverterExp = "1=Ⅰ等,2=Ⅱ等,3=Ⅲ等,4=Ⅳ等,5=Ⅴ等", combo = {"Ⅰ等","Ⅱ等","Ⅲ等","Ⅳ等","Ⅴ等"})
     private String engGrad;
     @Schema(description = "工程规模")
+    @Excel(name = "工程规模", sort = 10, readConverterExp = "1=大(1)型,2=大(2)型,3=中型,4=小(1)型,5=小(2)型", combo = {"大(1)型","大(2)型","中型","小(1)型","小(2)型"})
     private String engScal;
     @Schema(description = "流域面积")
+    @Excel(name = "坝址控制流域面积", sort = 11, suffix = " km²")
     private BigDecimal watShedArea;
     @Schema(description = "多年平均径流量")
+    @Excel(name = "坝址多年平均径流量", sort = 12)
     private BigDecimal daadMulAverRuof;
     @Schema(description = "上游防洪库容")
+    @Excel(name = "防洪高水位", sort = 13)
     private BigDecimal uppLevFlco;
     @Schema(description = "正常蓄水位")
+    @Excel(name = "正常蓄水位", sort = 14, suffix = " m")
     private BigDecimal normWatLev;
     @Schema(description = "正常库区面积")
+    @Excel(name = "正常蓄水位相应水面面积", sort = 15)
     private BigDecimal normPoolStagArea;
     @Schema(description = "正常库容")
+    @Excel(name = "正常蓄水位相应库容", sort = 16)
     private BigDecimal normPoolStagCap;
     @Schema(description = "防洪限制水位")
+    @Excel(name = "主汛期防洪限制水位", sort = 17)
     private BigDecimal flLowLimLev;
     @Schema(description = "防洪限制库容")
+    @Excel(name = "防洪限制水位库容", sort = 18)
     private BigDecimal flLowLimLevCap;
     @Schema(description = "死水位")
+    @Excel(name = "死水位", sort = 19, suffix = " m")
     private BigDecimal deadLev;
     @Schema(description = "总库容")
+    @Excel(name = "总库容", sort = 20, suffix = " 10⁴m³")
     private BigDecimal totCap;
     @Schema(description = "调洪库容")
+    @Excel(name = "调洪库容", sort = 21, suffix = " 10⁴m³")
     private BigDecimal storFlCap;
     @Schema(description = "防洪库容")
+    @Excel(name = "防洪库容", sort = 22, suffix = " 10⁴m³")
     private BigDecimal flcoCap;
     @Schema(description = "兴利库容")
+    @Excel(name = "兴利库容", sort = 23, suffix = " 10⁴m³")
     private BigDecimal benResCap;
     @Schema(description = "死库容")
+    @Excel(name = "死库容", sort = 24, suffix = " 10⁴m³")
     private BigDecimal deadCap;
     @Schema(description = "工程状态")
+    @Excel(name = "工程建设情况", sort = 25, readConverterExp = "1=已建,2=在建,3=规划", combo = {"已建","在建","规划"})
     private String engStat;
     @Schema(description = "运行状态")
     private String runStat;
     @Schema(description = "开工日期")
+    @Excel(name = "开工时间", sort = 26, dateFormat = "yyyy-MM-dd")
+    @JsonFormat(pattern = "yyyy-MM-dd")
     private Date startDate;
     @Schema(description = "竣工日期")
+    @Excel(name = "建成时间", sort = 27, dateFormat = "yyyy-MM-dd")
+    @JsonFormat(pattern = "yyyy-MM-dd")
     private Date compDate;
     @Schema(description = "管理单位")
+    @Excel(name = "归口管理部门", sort = 28, width = 20)
     private String admDep;
     @Schema(description = "备注")
+    @Excel(name = "备注", sort = 29, width = 20)
     private String note;
     @Schema(description = "更新日期")
     private Date updDate;

+ 24 - 0
gw-slgcrun/src/main/java/com/goldenwater/slgc/domain/AttWagaBase.java

@@ -1,6 +1,8 @@
 package com.goldenwater.slgc.domain;
 
+import com.goldenwater.common.annotation.Excel;
 import com.goldenwater.common.core.domain.BaseEntity;
+import com.fasterxml.jackson.annotation.JsonFormat;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.AllArgsConstructor;
 import lombok.Data;
@@ -19,50 +21,72 @@ public class AttWagaBase extends BaseEntity {
     private static final long serialVersionUID = 1L;
 
     @Schema(description = "泵站编码")
+    @Excel(name = "泵站代码", sort = 1, width = 20)
     private String wagaCode;
     @Schema(description = "采集日期")
     private Date collDate;
     @Schema(description = "泵站名称")
+    @Excel(name = "泵站名称", sort = 2, width = 20)
     private String wagaName;
     @Schema(description = "起点经度")
+    @Excel(name = "泵站经度", sort = 3, cellType = Excel.ColumnType.NUMERIC)
     private BigDecimal startLong;
     @Schema(description = "起点纬度")
+    @Excel(name = "泵站纬度", sort = 4, cellType = Excel.ColumnType.NUMERIC)
     private BigDecimal startLat;
     @Schema(description = "终点经度")
+    @Excel(name = "终点经度", sort = 18, cellType = Excel.ColumnType.NUMERIC)
     private BigDecimal endLong;
     @Schema(description = "终点纬度")
+    @Excel(name = "终点纬度", sort = 19, cellType = Excel.ColumnType.NUMERIC)
     private BigDecimal endLat;
     @Schema(description = "泵站位置")
+    @Excel(name = "泵站所在位置", sort = 5, width = 30)
     private String wagaLoc;
     @Schema(description = "泵站类型")
+    @Excel(name = "泵站类型", sort = 6, readConverterExp = "1=排水泵站,2=供水泵站,3=供排结合泵站", combo = {"排水泵站","供水泵站","供排结合泵站"})
     private String wagaType;
     @Schema(description = "泵站用途")
+    @Excel(name = "水闸用途", sort = 20)
     private String wagaUse;
     @Schema(description = "工程等级")
+    @Excel(name = "工程等别", sort = 9, readConverterExp = "1=Ⅰ等,2=Ⅱ等,3=Ⅲ等,4=Ⅳ等,5=Ⅴ等", combo = {"Ⅰ等","Ⅱ等","Ⅲ等","Ⅳ等","Ⅴ等"})
     private String engGrad;
     @Schema(description = "工程规模")
+    @Excel(name = "工程规模", sort = 10, readConverterExp = "1=大(1)型,2=大(2)型,3=中型,4=小(1)型,5=小(2)型", combo = {"大(1)型","大(2)型","中型","小(1)型","小(2)型"})
     private String engScal;
     @Schema(description = "主要水工类型")
     private String wainWasoType;
     @Schema(description = "通航吨位")
+    @Excel(name = "最大过闸流量", sort = 11)
     private BigDecimal lockDisc;
     @Schema(description = "泵站台数")
+    @Excel(name = "闸孔数量", sort = 7)
     private BigDecimal gaorNum;
     @Schema(description = "装机功率")
+    @Excel(name = "装机功率", sort = 8)
     private BigDecimal insPow;
     @Schema(description = "总装机容量")
+    @Excel(name = "装机流量", sort = 12, suffix = " m³/s")
     private BigDecimal desTotInsCap;
     @Schema(description = "工程状态")
+    @Excel(name = "工程建设情况", sort = 13, readConverterExp = "1=已建,2=在建,3=规划", combo = {"已建","在建","规划"})
     private String engStat;
     @Schema(description = "运行状态")
     private String runStat;
     @Schema(description = "开工日期")
+    @Excel(name = "开工时间", sort = 14, dateFormat = "yyyy-MM-dd")
+    @JsonFormat(pattern = "yyyy-MM-dd")
     private Date startDate;
     @Schema(description = "竣工日期")
+    @Excel(name = "建成时间", sort = 15, dateFormat = "yyyy-MM-dd")
+    @JsonFormat(pattern = "yyyy-MM-dd")
     private Date compDate;
     @Schema(description = "管理单位")
+    @Excel(name = "归口管理部门", sort = 16, width = 20)
     private String admDep;
     @Schema(description = "备注")
+    @Excel(name = "备注", sort = 17, width = 20)
     private String note;
     @Schema(description = "更新日期")
     private Date updDate;

+ 5 - 3
gw-slgcrun/src/main/java/com/goldenwater/slgc/domain/CmsArticle.java

@@ -1,6 +1,7 @@
 package com.goldenwater.slgc.domain;
 
 import com.goldenwater.common.annotation.Excel;
+import com.fasterxml.jackson.annotation.JsonFormat;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
@@ -36,7 +37,7 @@ public class CmsArticle {
     @Excel(name = "摘要")
     @Schema(description = "摘要")
     private String summary;
-    @Excel(name = "状态")
+    @Excel(name = "状态", readConverterExp = "0=草稿,1=已发布,2=已撤回,3=已删除", combo = {"草稿","已发布","已撤回","已删除"})
     @Schema(description = "状态")
     private String status;
     @Excel(name = "发布人")
@@ -44,6 +45,7 @@ public class CmsArticle {
     private String pubUser;
     @Excel(name = "发布日期", dateFormat = "yyyy-MM-dd")
     @Schema(description = "发布日期")
+    @JsonFormat(pattern = "yyyy-MM-dd")
     private Date pubDate;
     @Schema(description = "截止日期")
     private Date dueDate;
@@ -64,12 +66,12 @@ public class CmsArticle {
     private Integer commentCount;
     @Schema(description = "文章内容")
     private String content;
-    @Excel(name = "发布平台")
+    @Excel(name = "发布平台", readConverterExp = "1=PC,2=微信,3=PC+微信", combo = {"PC","微信","PC+微信"})
     @Schema(description = "发布平台")
     private String pubPlatform;
     @Schema(description = "标题颜色")
     private String titleColor;
-    @Excel(name = "类型")
+    @Excel(name = "类型", readConverterExp = "article=图文,link=链接", combo = {"图文","链接"})
     @Schema(description = "类型")
     private String type;
 

+ 8 - 0
gw-slgcrun/src/main/java/com/goldenwater/slgc/service/DikeService.java

@@ -55,4 +55,12 @@ public interface DikeService {
      * @return 类型分布统计结果
      */
     public List<Map<String, Object>> countType();
+
+    /**
+     * 导入堤防信息(按编码判重)
+     *
+     * @param list 堤防信息列表
+     * @return 导入结果信息
+     */
+    public String importDike(List<AttDikeBase> list);
 }

+ 8 - 0
gw-slgcrun/src/main/java/com/goldenwater/slgc/service/PustService.java

@@ -52,4 +52,12 @@ public interface PustService {
      * @return 规模分布统计结果
      */
     public List<Map<String, Object>> countType();
+
+    /**
+     * 导入水闸信息(按编码判重)
+     *
+     * @param list 水闸信息列表
+     * @return 导入结果信息
+     */
+    public String importPust(List<AttPustBase> list);
 }

+ 8 - 0
gw-slgcrun/src/main/java/com/goldenwater/slgc/service/ReservoirService.java

@@ -52,4 +52,12 @@ public interface ReservoirService {
      * @return 规模分布统计结果
      */
     public List<Map<String, Object>> countType();
+
+    /**
+     * 导入水库信息(按编码判重)
+     *
+     * @param list 水库信息列表
+     * @return 导入结果信息
+     */
+    public String importReservoir(List<AttResBase> list);
 }

+ 8 - 0
gw-slgcrun/src/main/java/com/goldenwater/slgc/service/WagaService.java

@@ -52,4 +52,12 @@ public interface WagaService {
      * @return 等级分布统计结果
      */
     public List<Map<String, Object>> countType();
+
+    /**
+     * 导入泵站信息(按编码判重)
+     *
+     * @param list 泵站信息列表
+     * @return 导入结果信息
+     */
+    public String importWaga(List<AttWagaBase> list);
 }

+ 40 - 1
gw-slgcrun/src/main/java/com/goldenwater/slgc/service/impl/CmsArticleServiceImpl.java

@@ -3,7 +3,12 @@ package com.goldenwater.slgc.service.impl;
 import java.util.List;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import com.goldenwater.common.utils.StringUtils;
+import com.goldenwater.common.utils.SecurityUtils;
+import com.goldenwater.common.utils.uuid.IdUtils;
 import com.goldenwater.slgc.domain.CmsArticle;
+import com.goldenwater.slgc.domain.CmsArticleFile;
+import com.goldenwater.slgc.mapper.CmsArticleFileMapper;
 import com.goldenwater.slgc.mapper.CmsArticleMapper;
 import com.goldenwater.slgc.service.CmsArticleService;
 
@@ -12,6 +17,8 @@ import com.goldenwater.slgc.service.CmsArticleService;
 public class CmsArticleServiceImpl implements CmsArticleService {
     @Autowired
     private CmsArticleMapper cmsArticleMapper;
+    @Autowired
+    private CmsArticleFileMapper cmsArticleFileMapper;
 
     @Override
     public List<CmsArticle> selectCmsArticleList(CmsArticle cmsArticle) {
@@ -20,7 +27,13 @@ public class CmsArticleServiceImpl implements CmsArticleService {
 
     @Override
     public CmsArticle selectCmsArticleById(String aid) {
-        return cmsArticleMapper.selectCmsArticleById(aid);
+        CmsArticle article = cmsArticleMapper.selectCmsArticleById(aid);
+        if (article != null) {
+            CmsArticleFile query = new CmsArticleFile();
+            query.setAid(aid);
+            article.setArticleFiles(cmsArticleFileMapper.selectCmsArticleFileList(query));
+        }
+        return article;
     }
 
     @Override
@@ -30,6 +43,13 @@ public class CmsArticleServiceImpl implements CmsArticleService {
 
     @Override
     public int insertCmsArticle(CmsArticle cmsArticle) {
+        if (cmsArticle.getAid() == null || cmsArticle.getAid().isEmpty()) {
+            cmsArticle.setAid(IdUtils.fastSimpleUUID());
+        }
+        // 作者为空时默认当前登录用户
+        if (cmsArticle.getPubUser() == null || cmsArticle.getPubUser().isEmpty()) {
+            cmsArticle.setPubUser(SecurityUtils.getUsername());
+        }
         return cmsArticleMapper.insertCmsArticle(cmsArticle);
     }
 
@@ -41,6 +61,11 @@ public class CmsArticleServiceImpl implements CmsArticleService {
     @Override
     @org.springframework.transaction.annotation.Transactional
     public int batchInsert(List<CmsArticle> list) {
+        for (CmsArticle article : list) {
+            if (article.getAid() == null || article.getAid().isEmpty()) {
+                article.setAid(IdUtils.fastSimpleUUID());
+            }
+        }
         return cmsArticleMapper.batchInsert(list);
     }
 
@@ -69,10 +94,24 @@ public class CmsArticleServiceImpl implements CmsArticleService {
                 if (com.goldenwater.common.utils.StringUtils.isEmpty(article.getTitle())) {
                     throw new RuntimeException("标题不能为空");
                 }
+                // 自动生成AID
+                if (article.getAid() == null || article.getAid().isEmpty()) {
+                    article.setAid(IdUtils.fastSimpleUUID());
+                }
                 // 指定了目标分类则统一归类(如法规导入到"法律法规"分类)
                 if (com.goldenwater.common.utils.StringUtils.isNotEmpty(cateId)) {
                     article.setCateId(cateId);
                 }
+                // 默认值:状态空则草稿,发布日期空则当前时间,作者空则当前登录用户
+                if (article.getStatus() == null || article.getStatus().isEmpty()) {
+                    article.setStatus("0");
+                }
+                if (article.getPubDate() == null) {
+                    article.setPubDate(new Date());
+                }
+                if (article.getPubUser() == null || article.getPubUser().isEmpty()) {
+                    article.setPubUser(SecurityUtils.getUsername());
+                }
                 article.setIsDel("0");
                 cmsArticleMapper.insertCmsArticle(article);
                 successNum++;

+ 51 - 0
gw-slgcrun/src/main/java/com/goldenwater/slgc/service/impl/DikeServiceImpl.java

@@ -2,8 +2,10 @@ package com.goldenwater.slgc.service.impl;
 
 import java.util.List;
 import java.util.Map;
+import java.util.Date;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import com.goldenwater.common.utils.StringUtils;
 import com.goldenwater.slgc.domain.AttDikeBase;
 import com.goldenwater.slgc.mapper.AttDikeBaseMapper;
 import com.goldenwater.slgc.service.DikeService;
@@ -31,11 +33,15 @@ public class DikeServiceImpl implements DikeService {
 
     @Override
     public int insertAttDikeBase(AttDikeBase attDikeBase) {
+        if (attDikeBase.getCollDate() == null) {
+            attDikeBase.setCollDate(new Date());
+        }
         return attDikeBaseMapper.insertAttDikeBase(attDikeBase);
     }
 
     @Override
     public int updateAttDikeBase(AttDikeBase attDikeBase) {
+        attDikeBase.setUpdDate(new Date());
         return attDikeBaseMapper.updateAttDikeBase(attDikeBase);
     }
 
@@ -43,4 +49,49 @@ public class DikeServiceImpl implements DikeService {
     public List<Map<String, Object>> countType() {
         return attDikeBaseMapper.countType();
     }
+
+    @Override
+    public String importDike(List<AttDikeBase> list) {
+        if (list == null || list.isEmpty()) {
+            return "导入数据为空";
+        }
+        int successNum = 0;
+        int updateNum = 0;
+        int skipNum = 0;
+        int failureNum = 0;
+        StringBuilder errorMsg = new StringBuilder();
+        StringBuilder failureMsg = new StringBuilder();
+        for (AttDikeBase attDikeBase : list) {
+            try {
+                if (attDikeBase.getCollDate() == null) {
+                    attDikeBase.setCollDate(new Date());
+                }
+                if (StringUtils.isEmpty(attDikeBase.getDikeCode())) {
+                    skipNum++;
+                    continue;
+                }
+                AttDikeBase existing = attDikeBaseMapper.selectAttDikeBaseById(attDikeBase.getDikeCode());
+                if (existing != null) {
+                    attDikeBase.setUpdDate(new Date());
+                    attDikeBaseMapper.updateAttDikeBase(attDikeBase);
+                    updateNum++;
+                } else {
+                    attDikeBaseMapper.insertAttDikeBase(attDikeBase);
+                    successNum++;
+                }
+            } catch (Exception e) {
+                failureNum++;
+                failureMsg.append("<br/>" + failureNum + "、" + attDikeBase.getDikeName() + " 导入失败:" + e.getMessage());
+            }
+        }
+        errorMsg.append("导入成功:").append(successNum).append("条,更新:").append(updateNum).append("条");
+        if (skipNum > 0) {
+            errorMsg.append(",跳过(编码为空):").append(skipNum).append("条");
+        }
+        if (failureNum > 0) {
+            errorMsg.append(",失败:").append(failureNum).append("条");
+            errorMsg.append("<br/>失败明细:").append(failureMsg);
+        }
+        return errorMsg.toString();
+    }
 }

+ 51 - 0
gw-slgcrun/src/main/java/com/goldenwater/slgc/service/impl/PustServiceImpl.java

@@ -2,8 +2,10 @@ package com.goldenwater.slgc.service.impl;
 
 import java.util.List;
 import java.util.Map;
+import java.util.Date;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import com.goldenwater.common.utils.StringUtils;
 import com.goldenwater.slgc.domain.AttPustBase;
 import com.goldenwater.slgc.mapper.AttPustBaseMapper;
 import com.goldenwater.slgc.service.PustService;
@@ -31,11 +33,15 @@ public class PustServiceImpl implements PustService {
 
     @Override
     public int insertAttPustBase(AttPustBase attPustBase) {
+        if (attPustBase.getCollDate() == null) {
+            attPustBase.setCollDate(new Date());
+        }
         return attPustBaseMapper.insertAttPustBase(attPustBase);
     }
 
     @Override
     public int updateAttPustBase(AttPustBase attPustBase) {
+        attPustBase.setUpdDate(new Date());
         return attPustBaseMapper.updateAttPustBase(attPustBase);
     }
 
@@ -43,4 +49,49 @@ public class PustServiceImpl implements PustService {
     public List<Map<String, Object>> countType() {
         return attPustBaseMapper.countType();
     }
+
+    @Override
+    public String importPust(List<AttPustBase> list) {
+        if (list == null || list.isEmpty()) {
+            return "导入数据为空";
+        }
+        int successNum = 0;
+        int updateNum = 0;
+        int skipNum = 0;
+        int failureNum = 0;
+        StringBuilder errorMsg = new StringBuilder();
+        StringBuilder failureMsg = new StringBuilder();
+        for (AttPustBase attPustBase : list) {
+            try {
+                if (attPustBase.getCollDate() == null) {
+                    attPustBase.setCollDate(new Date());
+                }
+                if (StringUtils.isEmpty(attPustBase.getPustCode())) {
+                    skipNum++;
+                    continue;
+                }
+                AttPustBase existing = attPustBaseMapper.selectAttPustBaseById(attPustBase.getPustCode());
+                if (existing != null) {
+                    attPustBase.setUpdDate(new Date());
+                    attPustBaseMapper.updateAttPustBase(attPustBase);
+                    updateNum++;
+                } else {
+                    attPustBaseMapper.insertAttPustBase(attPustBase);
+                    successNum++;
+                }
+            } catch (Exception e) {
+                failureNum++;
+                failureMsg.append("<br/>" + failureNum + "、" + attPustBase.getPustName() + " 导入失败:" + e.getMessage());
+            }
+        }
+        errorMsg.append("导入成功:").append(successNum).append("条,更新:").append(updateNum).append("条");
+        if (skipNum > 0) {
+            errorMsg.append(",跳过(编码为空):").append(skipNum).append("条");
+        }
+        if (failureNum > 0) {
+            errorMsg.append(",失败:").append(failureNum).append("条");
+            errorMsg.append("<br/>失败明细:").append(failureMsg);
+        }
+        return errorMsg.toString();
+    }
 }

+ 51 - 0
gw-slgcrun/src/main/java/com/goldenwater/slgc/service/impl/ReservoirServiceImpl.java

@@ -2,8 +2,10 @@ package com.goldenwater.slgc.service.impl;
 
 import java.util.List;
 import java.util.Map;
+import java.util.Date;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import com.goldenwater.common.utils.StringUtils;
 import com.goldenwater.slgc.domain.AttResBase;
 import com.goldenwater.slgc.mapper.AttResBaseMapper;
 import com.goldenwater.slgc.service.ReservoirService;
@@ -31,11 +33,15 @@ public class ReservoirServiceImpl implements ReservoirService {
 
     @Override
     public int insertAttResBase(AttResBase attResBase) {
+        if (attResBase.getCollDate() == null) {
+            attResBase.setCollDate(new Date());
+        }
         return attResBaseMapper.insertAttResBase(attResBase);
     }
 
     @Override
     public int updateAttResBase(AttResBase attResBase) {
+        attResBase.setUpdDate(new Date());
         return attResBaseMapper.updateAttResBase(attResBase);
     }
 
@@ -43,4 +49,49 @@ public class ReservoirServiceImpl implements ReservoirService {
     public List<Map<String, Object>> countType() {
         return attResBaseMapper.countType();
     }
+
+    @Override
+    public String importReservoir(List<AttResBase> list) {
+        if (list == null || list.isEmpty()) {
+            return "导入数据为空";
+        }
+        int successNum = 0;
+        int updateNum = 0;
+        int skipNum = 0;
+        int failureNum = 0;
+        StringBuilder errorMsg = new StringBuilder();
+        StringBuilder failureMsg = new StringBuilder();
+        for (AttResBase attResBase : list) {
+            try {
+                if (attResBase.getCollDate() == null) {
+                    attResBase.setCollDate(new Date());
+                }
+                if (StringUtils.isEmpty(attResBase.getResCode())) {
+                    skipNum++;
+                    continue;
+                }
+                AttResBase existing = attResBaseMapper.selectAttResBaseById(attResBase.getResCode());
+                if (existing != null) {
+                    attResBase.setUpdDate(new Date());
+                    attResBaseMapper.updateAttResBase(attResBase);
+                    updateNum++;
+                } else {
+                    attResBaseMapper.insertAttResBase(attResBase);
+                    successNum++;
+                }
+            } catch (Exception e) {
+                failureNum++;
+                failureMsg.append("<br/>" + failureNum + "、" + attResBase.getResName() + " 导入失败:" + e.getMessage());
+            }
+        }
+        errorMsg.append("导入成功:").append(successNum).append("条,更新:").append(updateNum).append("条");
+        if (skipNum > 0) {
+            errorMsg.append(",跳过(编码为空):").append(skipNum).append("条");
+        }
+        if (failureNum > 0) {
+            errorMsg.append(",失败:").append(failureNum).append("条");
+            errorMsg.append("<br/>失败明细:").append(failureMsg);
+        }
+        return errorMsg.toString();
+    }
 }

+ 51 - 0
gw-slgcrun/src/main/java/com/goldenwater/slgc/service/impl/WagaServiceImpl.java

@@ -2,8 +2,10 @@ package com.goldenwater.slgc.service.impl;
 
 import java.util.List;
 import java.util.Map;
+import java.util.Date;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import com.goldenwater.common.utils.StringUtils;
 import com.goldenwater.slgc.domain.AttWagaBase;
 import com.goldenwater.slgc.mapper.AttWagaBaseMapper;
 import com.goldenwater.slgc.service.WagaService;
@@ -31,11 +33,15 @@ public class WagaServiceImpl implements WagaService {
 
     @Override
     public int insertAttWagaBase(AttWagaBase attWagaBase) {
+        if (attWagaBase.getCollDate() == null) {
+            attWagaBase.setCollDate(new Date());
+        }
         return attWagaBaseMapper.insertAttWagaBase(attWagaBase);
     }
 
     @Override
     public int updateAttWagaBase(AttWagaBase attWagaBase) {
+        attWagaBase.setUpdDate(new Date());
         return attWagaBaseMapper.updateAttWagaBase(attWagaBase);
     }
 
@@ -43,4 +49,49 @@ public class WagaServiceImpl implements WagaService {
     public List<Map<String, Object>> countType() {
         return attWagaBaseMapper.countType();
     }
+
+    @Override
+    public String importWaga(List<AttWagaBase> list) {
+        if (list == null || list.isEmpty()) {
+            return "导入数据为空";
+        }
+        int successNum = 0;
+        int updateNum = 0;
+        int skipNum = 0;
+        int failureNum = 0;
+        StringBuilder errorMsg = new StringBuilder();
+        StringBuilder failureMsg = new StringBuilder();
+        for (AttWagaBase attWagaBase : list) {
+            try {
+                if (attWagaBase.getCollDate() == null) {
+                    attWagaBase.setCollDate(new Date());
+                }
+                if (StringUtils.isEmpty(attWagaBase.getWagaCode())) {
+                    skipNum++;
+                    continue;
+                }
+                AttWagaBase existing = attWagaBaseMapper.selectAttWagaBaseById(attWagaBase.getWagaCode());
+                if (existing != null) {
+                    attWagaBase.setUpdDate(new Date());
+                    attWagaBaseMapper.updateAttWagaBase(attWagaBase);
+                    updateNum++;
+                } else {
+                    attWagaBaseMapper.insertAttWagaBase(attWagaBase);
+                    successNum++;
+                }
+            } catch (Exception e) {
+                failureNum++;
+                failureMsg.append("<br/>" + failureNum + "、" + attWagaBase.getWagaName() + " 导入失败:" + e.getMessage());
+            }
+        }
+        errorMsg.append("导入成功:").append(successNum).append("条,更新:").append(updateNum).append("条");
+        if (skipNum > 0) {
+            errorMsg.append(",跳过(编码为空):").append(skipNum).append("条");
+        }
+        if (failureNum > 0) {
+            errorMsg.append(",失败:").append(failureNum).append("条");
+            errorMsg.append("<br/>失败明细:").append(failureMsg);
+        }
+        return errorMsg.toString();
+    }
 }

+ 5 - 2
gw-slgcrun/src/main/resources/mapper/slgc/AttDikeBaseMapper.xml

@@ -48,6 +48,9 @@
             <if test="dikeName != null and dikeName != ''">
                 and DIKE_NAME like concat('%', #{dikeName}, '%')
             </if>
+            <if test="startLoc != null and startLoc != ''">
+                and START_LOC like concat('%', #{startLoc}, '%')
+            </if>
             <if test="dikeGrad != null and dikeGrad != ''">
                 and DIKE_GRAD = #{dikeGrad}
             </if>
@@ -195,8 +198,8 @@
                  WHEN '4' THEN '四级'
                  WHEN '5' THEN '五级'
                  ELSE '其他'
-               END as name,
-               count(*) as value
+               END as "name",
+               count(*) as "value"
         from ATT_DIKE_BASE
         group by DIKE_GRAD
     </select>

+ 13 - 4
gw-slgcrun/src/main/resources/mapper/slgc/AttPustBaseMapper.xml

@@ -46,6 +46,9 @@
             <if test="pustName != null and pustName != ''">
                 and PUST_NAME like concat('%', #{pustName}, '%')
             </if>
+            <if test="pustLoc != null and pustLoc != ''">
+                and PUST_LOC like concat('%', #{pustLoc}, '%')
+            </if>
             <if test="pustType != null and pustType != ''">
                 and PUST_TYPE = #{pustType}
             </if>
@@ -174,10 +177,16 @@
     </update>
 
     <select id="countType" resultType="java.util.Map">
-        select TYPE as name, count(*) as value
-        from ATT_PUST_BASE
-        where TYPE is not null
-        group by TYPE
+        select a.type as "name", count(a.type) as "value" from
+        ( SELECT
+            CASE
+                WHEN ENG_GRAD &lt;= 2 THEN '大型'
+                WHEN ENG_GRAD = 3 THEN '中型'
+                WHEN ENG_GRAD >= 4 THEN '小型'
+                ELSE '其他'
+            END AS type
+          FROM ATT_PUST_BASE) a
+        group by a.type
     </select>
 
 </mapper>

+ 13 - 4
gw-slgcrun/src/main/resources/mapper/slgc/AttResBaseMapper.xml

@@ -53,6 +53,9 @@
             <if test="resName != null and resName != ''">
                 and RES_NAME like concat('%', #{resName}, '%')
             </if>
+            <if test="resLoc != null and resLoc != ''">
+                and RES_LOC like concat('%', #{resLoc}, '%')
+            </if>
             <if test="resType != null and resType != ''">
                 and RES_TYPE = #{resType}
             </if>
@@ -202,10 +205,16 @@
     </update>
 
     <select id="countType" resultType="java.util.Map">
-        select TYPE as name, count(*) as value
-        from ATT_RES_BASE
-        where TYPE is not null
-        group by TYPE
+        select a.type as "name", count(a.type) as "value" from
+        ( SELECT
+            CASE
+                WHEN TOT_CAP >= 10000 THEN '大型'
+                WHEN TOT_CAP > 1000 THEN '中型'
+                WHEN TOT_CAP &lt;= 1000 THEN '小型'
+                ELSE '其他'
+            END AS type
+          FROM ATT_RES_BASE) a
+        group by a.type
     </select>
 
 </mapper>

+ 13 - 4
gw-slgcrun/src/main/resources/mapper/slgc/AttWagaBaseMapper.xml

@@ -44,6 +44,9 @@
             <if test="wagaName != null and wagaName != ''">
                 and WAGA_NAME like concat('%', #{wagaName}, '%')
             </if>
+            <if test="wagaLoc != null and wagaLoc != ''">
+                and WAGA_LOC like concat('%', #{wagaLoc}, '%')
+            </if>
             <if test="wagaType != null and wagaType != ''">
                 and WAGA_TYPE = #{wagaType}
             </if>
@@ -169,10 +172,16 @@
     </update>
 
     <select id="countType" resultType="java.util.Map">
-        select TYPE as name, count(*) as value
-        from ATT_WAGA_BASE
-        where TYPE is not null
-        group by TYPE
+        select a.type as "name", count(a.type) as "value" from
+        ( SELECT
+            CASE
+                WHEN LOCK_DISC >= 50 THEN '大型'
+                WHEN LOCK_DISC > 10 THEN '中型'
+                WHEN LOCK_DISC &lt;= 10 THEN '小型'
+                ELSE '其他'
+            END AS type
+          FROM ATT_WAGA_BASE) a
+        group by a.type
     </select>
 
 </mapper>

+ 4 - 1
gw-slgcrun/src/main/resources/mapper/slgc/CmsArticleMapper.xml

@@ -45,7 +45,7 @@
                 and TITLE like concat('%', #{title}, '%')
             </if>
             <if test="cateId != null and cateId != ''">
-                and CATE_ID = #{cateId}
+                and (CATE_ID = #{cateId} or CATE_ID in (select CATE_ID from BIZ_CMS_CATE start with CATE_ID = #{cateId} connect by prior CATE_ID = CATE_PID))
             </if>
             <if test="keyword != null and keyword != ''">
                 and KEYWORD like concat('%', #{keyword}, '%')
@@ -53,6 +53,9 @@
             <if test="status != null and status != ''">
                 and STATUS = #{status}
             </if>
+            <if test="status == null or status == ''">
+                and IS_DEL != '1'
+            </if>
             <if test="bizDept != null and bizDept != ''">
                 and BIZ_DEPT = #{bizDept}
             </if>

+ 4 - 0
gw-ui/src/api/slgc/dike/index.js

@@ -19,3 +19,7 @@ export function delDike(dikeCodes) {
 export function getDikeCountType() {
   return request({ url: '/slgc/dike/countType', method: 'get' })
 }
+
+export function exportDike(queryParams) {
+  return request({ url: '/slgc/dike/export', method: 'get', params: queryParams, responseType: 'blob' })
+}

+ 1 - 0
gw-ui/src/api/slgc/doc/index.js

@@ -11,6 +11,7 @@ export function getDocFileList(params) { return request({ url: '/slgc/doc-file/l
 export function getDocFile(id) { return request({ url: '/slgc/doc-file/' + id, method: 'get' }) }
 export function addDocFile(data) { return request({ url: '/slgc/doc-file', method: 'post', data }) }
 export function updateDocFile(data) { return request({ url: '/slgc/doc-file', method: 'put', data }) }
+export function renameDocFile(fileId, fileName) { return request({ url: '/slgc/doc-file/rename', method: 'put', params: { fileId, fileName } }) }
 export function delDocFile(ids) { return request({ url: '/slgc/doc-file/' + ids, method: 'delete' }) }
 export function moveDocFile(data) {
   const params = new URLSearchParams()

+ 4 - 0
gw-ui/src/api/slgc/pust/index.js

@@ -16,3 +16,7 @@ export function delPust(pustCodes) {
 export function getPustCountType() {
   return request({ url: '/slgc/pust/countType', method: 'get' })
 }
+
+export function exportPust(queryParams) {
+  return request({ url: '/slgc/pust/export', method: 'get', params: queryParams, responseType: 'blob' })
+}

+ 4 - 0
gw-ui/src/api/slgc/reservoir/index.js

@@ -19,3 +19,7 @@ export function delReservoir(resCodes) {
 export function getReservoirCountType() {
   return request({ url: '/slgc/reservoir/countType', method: 'get' })
 }
+
+export function exportReservoir(queryParams) {
+  return request({ url: '/slgc/reservoir/export', method: 'get', params: queryParams, responseType: 'blob' })
+}

+ 4 - 0
gw-ui/src/api/slgc/waga/index.js

@@ -16,3 +16,7 @@ export function delWaga(wagaCodes) {
 export function getWagaCountType() {
   return request({ url: '/slgc/waga/countType', method: 'get' })
 }
+
+export function exportWaga(queryParams) {
+  return request({ url: '/slgc/waga/export', method: 'get', params: queryParams, responseType: 'blob' })
+}

+ 76 - 53
gw-ui/src/components/ExcelImportDialog/index.vue

@@ -1,21 +1,25 @@
 <template>
   <el-dialog :title="title" v-model="visible" :width="width" append-to-body @close="handleClose">
-    <el-upload ref="uploadRef" :limit="1" accept=".xlsx, .xls" :headers="headers" :action="uploadUrl" :disabled="isUploading" :on-progress="handleProgress" :on-change="handleFileChange" :on-remove="handleFileRemove" :on-success="handleSuccess" :auto-upload="false" drag>
+    <el-upload ref="uploadRef" :limit="1" accept=".xlsx, .xls" :headers="headers" :action="uploadUrl" :disabled="isUploading" :on-progress="handleProgress" :on-change="handleFileChange" :on-remove="handleFileRemove" :on-success="handleSuccess" :auto-upload="false" :show-file-list="true" drag>
       <el-icon class="el-icon--upload"><upload-filled /></el-icon>
       <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
       <template #tip>
         <div class="el-upload__tip text-center">
-          <div class="el-upload__tip">
-            <el-checkbox v-model="updateSupport"> {{ updateSupportLabel }} </el-checkbox>
-          </div>
           <span>仅允许导入xls、xlsx格式文件。</span>
           <el-link v-if="templateUrl" type="primary" underline="never" style="font-size: 12px; vertical-align: baseline" @click="handleDownloadTemplate">下载模板</el-link>
         </div>
       </template>
     </el-upload>
+    <!-- 手动显示已选文件名(drag模式下el-upload可能不显示文件列表) -->
+    <div v-if="selectedFile && !isUploading" class="selected-file-info">
+      <el-icon><Document /></el-icon>
+      <span class="file-name">{{ selectedFile.name }}</span>
+      <span class="file-size">({{ formatSize(selectedFile.size) }})</span>
+      <el-button type="danger" link icon="Delete" @click="clearSelectedFile">移除</el-button>
+    </div>
     <template #footer>
       <div class="dialog-footer">
-        <el-button type="primary" @click="handleSubmit">确 定</el-button>
+        <el-button type="primary" :loading="isUploading" @click="handleSubmit">{{ isUploading ? '导入中...' : '确 定' }}</el-button>
         <el-button @click="visible = false">取 消</el-button>
       </div>
     </template>
@@ -23,41 +27,18 @@
 </template>
 
 <script setup>
+import { Document } from '@element-plus/icons-vue'
 import { getToken } from '@/utils/auth'
+import request from '@/utils/request'
 
 const { proxy } = getCurrentInstance()
 
 const props = defineProps({
-  // 对话框标题
-  title: {
-    type: String,
-    default: '数据导入'
-  },
-  // 对话框宽度
-  width: {
-    type: String,
-    default: '400px'
-  },
-  // 上传接口地址(必传)
-  action: {
-    type: String,
-    required: true
-  },
-  // 模板下载接口地址,不传则不显示下载模板链接
-  templateAction: {
-    type: String,
-    default: ''
-  },
-  // 模板文件名前缀
-  templateFileName: {
-    type: String,
-    default: 'template'
-  },
-  // 覆盖更新勾选框的说明文字
-  updateSupportLabel: {
-    type: String,
-    default: '是否更新已经存在的数据'
-  }
+  title: { type: String, default: '数据导入' },
+  width: { type: String, default: '400px' },
+  action: { type: String, required: true },
+  templateAction: { type: String, default: '' },
+  templateFileName: { type: String, default: 'template' }
 })
 
 const emit = defineEmits(['success'])
@@ -66,18 +47,22 @@ const uploadRef = ref(null)
 const visible = ref(false)
 const selectedFile = ref(null)
 const isUploading = ref(false)
-const updateSupport = ref(false)
 const headers = { Authorization: 'Bearer ' + getToken() }
 
 const uploadUrl = computed(() => {
-  return import.meta.env.VITE_SERVICE_BASE_TITLE + props.action + '?updateSupport=' + (updateSupport.value ? 1 : 0)
+  return import.meta.env.VITE_SERVICE_BASE_TITLE + props.action
 })
 
 const templateUrl = computed(() => !!props.templateAction)
 
-// 打开对话框(供父组件通过 ref 调用)
+function formatSize(bytes) {
+  if (!bytes) return '0 B'
+  if (bytes < 1024) return bytes + ' B'
+  if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'
+  return (bytes / 1024 / 1024).toFixed(1) + ' MB'
+}
+
 function open() {
-  updateSupport.value = false
   isUploading.value = false
   visible.value = true
   nextTick(() => {
@@ -86,34 +71,41 @@ function open() {
   })
 }
 
-// 关闭时清理
 function handleClose() {
   isUploading.value = false
   selectedFile.value = null
   uploadRef.value?.clearFiles()
 }
 
-// 下载模板
 function handleDownloadTemplate() {
-  proxy.download(props.templateAction, {}, `${props.templateFileName}_${new Date().getTime()}.xlsx`)
+  request({ url: props.templateAction, method: 'get', responseType: 'blob' }).then(res => {
+    const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
+    const url = window.URL.createObjectURL(blob)
+    const link = document.createElement('a')
+    link.href = url
+    link.download = `${props.templateFileName}_${new Date().getTime()}.xlsx`
+    link.click()
+    window.URL.revokeObjectURL(url)
+  })
 }
 
-// 上传进度
 function handleProgress() {
   isUploading.value = true
 }
 
-/** 文件选择处理 */
-const handleFileChange = (file, fileList) => {
-  selectedFile.value = file
+const handleFileChange = (file) => {
+  selectedFile.value = file.raw || file
 }
 
-/** 文件删除处理 */
-const handleFileRemove = (file, fileList) => {
+const handleFileRemove = () => {
   selectedFile.value = null
 }
 
-// 上传成功
+function clearSelectedFile() {
+  selectedFile.value = null
+  uploadRef.value?.clearFiles()
+}
+
 function handleSuccess(response) {
   visible.value = false
   isUploading.value = false
@@ -123,15 +115,46 @@ function handleSuccess(response) {
   emit('success')
 }
 
-// 提交上传
 function handleSubmit() {
-  const file = selectedFile.value
-  if (!file || file.length === 0 || !file.name.toLowerCase().endsWith('.xls') && !file.name.toLowerCase().endsWith('.xlsx')) {
-    proxy.$modal.msgError("请选择后缀为 “xls”或“xlsx”的文件。")
+  if (!selectedFile.value) {
+    proxy.$modal.msgError("请选择要导入的Excel文件。")
     return
   }
+  const name = selectedFile.value.name || ''
+  if (!name.toLowerCase().endsWith('.xls') && !name.toLowerCase().endsWith('.xlsx')) {
+    proxy.$modal.msgError("请选择后缀为 xls 或 xlsx 的文件。")
+    return
+  }
+  isUploading.value = true
   uploadRef.value.submit()
 }
 
 defineExpose({ open })
 </script>
+
+<style scoped>
+.selected-file-info {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-top: 12px;
+  padding: 8px 12px;
+  background: #f5f7fa;
+  border: 1px solid #e4e7ed;
+  border-radius: 4px;
+  font-size: 13px;
+  color: #606266;
+}
+.selected-file-info .file-name {
+  flex: 1;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  color: #303133;
+  font-weight: 500;
+}
+.selected-file-info .file-size {
+  color: #909399;
+  flex-shrink: 0;
+}
+</style>

+ 1 - 0
gw-ui/src/views/monitor/operlog/detail.vue

@@ -112,6 +112,7 @@
 </template>
 
 <script setup>
+import { ElMessage, ElMessageBox } from 'element-plus'
 const { proxy } = getCurrentInstance()
 
 const props = defineProps({

+ 1 - 0
gw-ui/src/views/slgc/cms/article/detail.vue

@@ -73,6 +73,7 @@
 </template>
 
 <script setup name="CmsDetail">
+import { ElMessage, ElMessageBox } from 'element-plus'
 import { getArticleDetail, getCateTree } from '@/api/slgc/cms/index'
 import { Printer } from '@element-plus/icons-vue'
 

+ 56 - 77
gw-ui/src/views/slgc/cms/article/index.vue

@@ -37,27 +37,11 @@
 
       <!-- 状态 Tabs -->
       <el-tabs v-model="queryParams.status" class="status-tabs" @tab-change="handleTabChange">
-        <el-tab-pane label="全部" name="undefined" />
-        <el-tab-pane :name="'0'">
-          <template #label>
-            <span>草稿 <el-badge :value="statusCounts[0]" :max="999" :hidden="!statusCounts[0]" class="tab-badge" /></span>
-          </template>
-        </el-tab-pane>
-        <el-tab-pane :name="'1'">
-          <template #label>
-            <span>已发布 <el-badge :value="statusCounts[1]" :max="999" :hidden="!statusCounts[1]" class="tab-badge" /></span>
-          </template>
-        </el-tab-pane>
-        <el-tab-pane :name="'2'">
-          <template #label>
-            <span>已撤回 <el-badge :value="statusCounts[2]" :max="999" :hidden="!statusCounts[2]" class="tab-badge" /></span>
-          </template>
-        </el-tab-pane>
-        <el-tab-pane :name="'3'">
-          <template #label>
-            <span>已删除 <el-badge :value="statusCounts[3]" :max="999" :hidden="!statusCounts[3]" class="tab-badge" type="info" /></span>
-          </template>
-        </el-tab-pane>
+        <el-tab-pane label="全部" name="" />
+        <el-tab-pane label="草稿" name="0" />
+        <el-tab-pane label="已发布" name="1" />
+        <el-tab-pane label="已撤回" name="2" />
+        <el-tab-pane label="已删除" name="3" />
       </el-tabs>
 
       <!-- 搜索栏 -->
@@ -153,11 +137,13 @@
           </template>
         </el-table-column>
         <el-table-column prop="viewCount" label="点击量" width="80" sortable="custom" />
-        <el-table-column label="操作" width="180" fixed="right">
+        <el-table-column label="操作" width="230" fixed="right">
           <template #default="s">
             <div class="op-group">
               <el-button type="primary" link @click="handleDetail(s.row)">查看</el-button>
               <el-button type="primary" link @click="handleEdit(s.row)">编辑</el-button>
+              <el-button v-if="s.row.status === '0' || s.row.status === '2'" type="success" link @click="handlePublish(s.row)">发布</el-button>
+              <el-button v-if="s.row.status === '1'" type="warning" link @click="handleRecallOne(s.row)">撤回</el-button>
               <el-button type="danger" link @click="handleDelete(s.row)">删除</el-button>
             </div>
           </template>
@@ -255,7 +241,7 @@
               <el-upload
                 ref="thumbUploadRef"
                 class="thumbnail-uploader"
-                action="/common/upload"
+                :action="commonUploadUrl"
                 :show-file-list="false"
                 :on-success="handleThumbSuccess"
                 :before-upload="beforeThumbUpload"
@@ -287,7 +273,7 @@
         <el-form-item label="附件">
           <el-upload
             ref="uploadRef"
-            action="/common/upload"
+            :action="commonUploadUrl"
             :auto-upload="false"
             :on-change="handleFileChange"
             :on-remove="handleFileRemove"
@@ -442,14 +428,17 @@
 <script setup name="CmsArticle">
 import { Paperclip, Check, Top, RefreshLeft, Delete, DeleteFilled, Plus, CopyDocument, FolderOpened } from '@element-plus/icons-vue'
 import { getToken } from '@/utils/auth'
+import request from '@/utils/request'
 import { getArticleList, getArticleDetail, addArticle, updateArticle, delArticle, getCateTree, batchAddArticle, batchUpdateArticle, exportArticle, importTemplateUrl } from '@/api/slgc/cms/index'
 import Editor from '@/components/Editor/index'
 
 const { proxy } = getCurrentInstance()
+// 上传接口(拼接 base URL)
+const commonUploadUrl = import.meta.env.VITE_SERVICE_BASE_TITLE + '/common/upload'
 // 导入导出
 const importVisible = ref(false)
 const importUploadRef = ref(null)
-const importUrl = ref('/slgc/cms-article/importData')
+const importUrl = ref(import.meta.env.VITE_SERVICE_BASE_TITLE + '/slgc/cms-article/importData')
 const importCateId = ref('')
 const uploadHeaders = ref({ Authorization: 'Bearer ' + getToken() })
 const loading = ref(true)
@@ -483,7 +472,7 @@ const queryParams = reactive({
   pageNum: 1,
   pageSize: 10,
   title: undefined,
-  status: undefined,
+  status: '',
   cateId: undefined,
   orderByColumn: undefined,
   isAsc: undefined
@@ -493,9 +482,6 @@ const defaultSort = ref({ prop: 'pubDate', order: 'descending' })
 const sidebarTreeData = computed(() => {
   return [{ cateId: '', cateName: '全部', children: cateTreeData.value }]
 })
-
-const statusCounts = reactive({ 0: 0, 1: 0, 2: 0, 3: 0 })
-
 const form = ref({})
 
 const formRules = {
@@ -583,33 +569,12 @@ function getCates() {
   })
 }
 
-/** 加载各状态数量 */
-function loadStatusCounts() {
-      getArticleList({ pageNum: 1, pageSize: 1 }).then(res => {
-        // 首次加载获取各状态数量(接口支持 status 过滤则分别获取)
-        const statuses = ['0', '1', '2', '3']
-        statuses.forEach(s => {
-          getArticleList({ pageNum: 1, pageSize: 1, status: s }).then(r => {
-            statusCounts[s] = r.total || 0
-          }).catch(() => {
-            statusCounts[s] = 0
-          })
-        })
-      }).catch(() => {
-        // 统计接口失败时全部置 0,避免角标异常
-        statusCounts['0'] = 0
-        statusCounts['1'] = 0
-        statusCounts['2'] = 0
-        statusCounts['3'] = 0
-      })
-    }
-
 /** 获取文章列表 */
 function getList() {
   loading.value = true
   const params = { ...queryParams }
-  // 将 status 为 'undefined' 字符串转为 undefined
-  if (params.status === 'undefined' || params.status === undefined) {
+  // 将 status 为空字符串或 'undefined' 转为 undefined(全部)
+  if (params.status === '' || params.status === 'undefined' || params.status === undefined) {
     params.status = undefined
   }
   getArticleList(params).then(res => {
@@ -637,7 +602,7 @@ function handleQuery() {
 function resetQuery() {
   queryParams.title = undefined
   queryParams.cateId = undefined
-  queryParams.status = undefined
+  queryParams.status = ''
   cateTreeRef.value?.setCurrentKey('')
   handleQuery()
 }
@@ -678,7 +643,7 @@ function handleDetail(row) {
   detailVisible.value = true
   getArticleDetail(row.aid).then(res => {
     detailData.value = res.data || {}
-    detailFiles.value = res.data?.files || []
+    detailFiles.value = res.data?.articleFiles || []
     detailLoading.value = false
   }).catch(() => {
     detailLoading.value = false
@@ -688,7 +653,7 @@ function handleDetail(row) {
 
 /** 新增 */
 function handleAdd() {
-  form.value = { type: 'article', pubPlatform: [], auditUser: '', url: '' }
+  form.value = { type: 'article', pubPlatform: [], auditUser: '', url: '', status: '0' }
   fileList.value = []
   title.value = '新增文章'
   open.value = true
@@ -704,7 +669,7 @@ function openImportDialog() {
 function handleExport() {
   proxy.$modal.confirm('确认导出当前筛选条件下的全部数据?').then(() => {
     const params = { ...queryParams }
-    if (params.status === 'undefined' || params.status === undefined) params.status = undefined
+    if (params.status === '' || params.status === 'undefined' || params.status === undefined) params.status = undefined
     exportArticle(params).then(res => {
       const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
       const url = window.URL.createObjectURL(blob)
@@ -719,7 +684,15 @@ function handleExport() {
 
 /** 下载导入模板 */
 function handleDownloadTemplate() {
-  window.open(importTemplateUrl(), '_blank')
+  request({ url: importTemplateUrl(), method: 'get', responseType: 'blob' }).then(res => {
+    const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
+    const url = window.URL.createObjectURL(blob)
+    const link = document.createElement('a')
+    link.href = url
+    link.download = '内容文章导入模板.xlsx'
+    link.click()
+    window.URL.revokeObjectURL(url)
+  })
 }
 
 /** 导入成功 */
@@ -728,7 +701,6 @@ function handleImportSuccess(res) {
     proxy.$modal.msgSuccess(res.msg || '导入成功')
     importVisible.value = false
     getList()
-    loadStatusCounts()
   } else {
     proxy.$modal.msgError(res.msg || '导入失败')
   }
@@ -746,7 +718,7 @@ function handleEdit(row) {
   getArticleDetail(row.aid).then(res => {
     form.value = { ...res.data }
     form.value.pubPlatform = pubPlatformToArray(res.data.pubPlatform)
-    fileList.value = res.data?.files ? [...res.data.files] : []
+    fileList.value = res.data?.articleFiles ? [...res.data.articleFiles] : []
     title.value = '编辑文章'
     open.value = true
   })
@@ -755,10 +727,29 @@ function handleEdit(row) {
 /** 单条删除(软删除,移至回收站) */
 function handleDelete(row) {
   proxy.$modal.confirm('确认将文章"' + row.title + '"移至回收站?').then(() => {
-    batchUpdateArticle([{ aid: row.aid, isDel: '1' }]).then(() => {
+    batchUpdateArticle([{ aid: row.aid, isDel: '1', status: '3' }]).then(() => {
       proxy.$modal.msgSuccess('已移至回收站')
       getList()
-      loadStatusCounts()
+    })
+  })
+}
+
+/** 单条发布 */
+function handlePublish(row) {
+  proxy.$modal.confirm('确认发布文章"' + row.title + '"?').then(() => {
+    batchUpdateArticle([{ aid: row.aid, status: '1', pubDate: new Date().toISOString().slice(0, 10) }]).then(() => {
+      proxy.$modal.msgSuccess('发布成功')
+      getList()
+    })
+  })
+}
+
+/** 单条撤回 */
+function handleRecallOne(row) {
+  proxy.$modal.confirm('确认撤回文章"' + row.title + '"?').then(() => {
+    batchUpdateArticle([{ aid: row.aid, status: '2' }]).then(() => {
+      proxy.$modal.msgSuccess('已撤回')
+      getList()
     })
   })
 }
@@ -809,7 +800,6 @@ function submitForm() {
       proxy.$modal.msgSuccess('操作成功')
       open.value = false
       getList()
-      loadStatusCounts()
     })
   })
 }
@@ -853,11 +843,10 @@ function handleBatchPublish() {
   const aids = getSelectedAids()
   if (!aids) return
   proxy.$modal.confirm(`确认发布选中的 ${selectedRows.value.length} 篇文章?`).then(() => {
-    updateArticle({ aids, status: '1' }).then(() => {
+    batchUpdateArticle(selectedRows.value.map(r => ({ aid: r.aid, status: '1' }))).then(() => {
       proxy.$modal.msgSuccess('批量发布成功')
       clearSelection()
       getList()
-      loadStatusCounts()
     })
   })
 }
@@ -867,7 +856,7 @@ function handleBatchTop() {
   const aids = getSelectedAids()
   if (!aids) return
   proxy.$modal.confirm(`确认置顶选中的 ${selectedRows.value.length} 篇文章?`).then(() => {
-    updateArticle({ aids, isTop: 1 }).then(() => {
+    batchUpdateArticle(selectedRows.value.map(r => ({ aid: r.aid, isTop: 1 }))).then(() => {
       proxy.$modal.msgSuccess('批量置顶成功')
       clearSelection()
       getList()
@@ -880,11 +869,10 @@ function handleBatchRecall() {
   const aids = getSelectedAids()
   if (!aids) return
   proxy.$modal.confirm(`确认撤回选中的 ${selectedRows.value.length} 篇文章?`).then(() => {
-    updateArticle({ aids, status: '2' }).then(() => {
+    batchUpdateArticle(selectedRows.value.map(r => ({ aid: r.aid, status: '2' }))).then(() => {
       proxy.$modal.msgSuccess('批量撤回成功')
       clearSelection()
       getList()
-      loadStatusCounts()
     })
   })
 }
@@ -953,12 +941,11 @@ function handleBatchDelete() {
   const aids = getSelectedAids()
   if (!aids) return
   proxy.$modal.confirm(`确认将选中的 ${selectedRows.value.length} 篇文章移至回收站?`).then(() => {
-    const items = selectedRows.value.map(r => ({ aid: r.aid, isDel: '1' }))
+    const items = selectedRows.value.map(r => ({ aid: r.aid, isDel: '1', status: '3' }))
     batchUpdateArticle(items).then(() => {
       proxy.$modal.msgSuccess('已移至回收站')
       clearSelection()
       getList()
-      loadStatusCounts()
     })
   })
 }
@@ -972,7 +959,6 @@ function handleBatchDrop() {
       proxy.$modal.msgSuccess('批量清空成功')
       clearSelection()
       getList()
-      loadStatusCounts()
     })
   })
 }
@@ -980,7 +966,6 @@ function handleBatchDrop() {
 onMounted(() => {
   getCates()
   getList()
-  loadStatusCounts()
 })
 </script>
 
@@ -1071,12 +1056,6 @@ onMounted(() => {
 .status-tabs :deep(.el-tabs__item) {
   font-size: 14px;
 }
-.tab-badge {
-  margin-left: 4px;
-}
-.tab-badge :deep(.el-badge__content) {
-  font-size: 10px;
-}
 
 .batch-toolbar {
   display: flex;

+ 1 - 0
gw-ui/src/views/slgc/cms/category/index.vue

@@ -38,6 +38,7 @@
 </template>
 
 <script setup name="CmsCategory">
+import { ElMessage, ElMessageBox } from 'element-plus'
 import { getCateList, getCateTree, getCate, addCate, updateCate, delCate } from '@/api/slgc/cms/index'
 const { proxy } = getCurrentInstance()
 const loading = ref(true)

+ 171 - 71
gw-ui/src/views/slgc/dike/index.vue

@@ -1,144 +1,244 @@
-<template>
+<template>
   <div class="gc-page">
-    <!-- 顶部工具栏 -->
     <div class="gc-toolbar">
-      <el-button type="success" icon="DataAnalysis" @click="handleStats">统计</el-button>
+      <div class="gc-toolbar-left">
+        <el-button type="success" icon="DataAnalysis" @click="handleStats">统计</el-button>
+        <el-button type="primary" icon="Plus" @click="handleAdd">新增</el-button>
+        <el-button type="warning" icon="Edit" :disabled="single" @click="handleUpdate">修改</el-button>
+        <el-button type="danger" icon="Delete" :disabled="multiple" @click="handleBatchDelete">删除</el-button>
+        <el-button type="info" icon="Upload" @click="importRef.open()">导入</el-button>
+        <el-button type="success" icon="Download" @click="handleExport">导出</el-button>
+      </div>
       <div class="gc-toolbar-right">
         <el-input v-model="queryParams.dikeName" placeholder="请输入名称" clearable style="width: 160px" @keyup.enter="handleQuery" />
         <el-input v-model="queryParams.startLoc" placeholder="请输入地址(起点终点都可)" clearable style="width: 200px" @keyup.enter="handleQuery" />
         <el-select v-model="queryParams.dikePatt" placeholder="请选择类别-" clearable style="width: 140px">
-          <el-option label="土堤" value="1" />
-          <el-option label="砌石堤" value="2" />
-          <el-option label="土石混合堤" value="3" />
-          <el-option label="钢筋混凝土防洪墙" value="4" />
-          <el-option label="其他" value="9" />
+          <el-option label="土堤" value="1" /><el-option label="砌石堤" value="2" /><el-option label="土石混合堤" value="3" /><el-option label="钢筋混凝土防洪墙" value="4" /><el-option label="其他" value="9" />
         </el-select>
         <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
       </div>
     </div>
 
-    <!-- 数据表格 -->
-    <el-table v-loading="loading" :data="list" stripe border style="width: 100%">
+    <el-table v-loading="loading" :data="list" stripe border style="width: 100%" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
       <el-table-column prop="dikeName" label="堤防名称" min-width="180" />
       <el-table-column prop="startLoc" label="起始所在位置" min-width="200" />
       <el-table-column prop="endLoc" label="终点所在位置" min-width="200" />
       <el-table-column prop="dikeType" label="堤防类型" width="120" />
       <el-table-column prop="dikeGrad" label="堤防级别" width="100">
-        <template #default="scope">{{ gradeMap[scope.row.dikeGrad] || scope.row.dikeGrad }}</template>
+        <template #default="s">{{ gradeMap[s.row.dikeGrad] || s.row.dikeGrad }}</template>
       </el-table-column>
       <el-table-column prop="dikePatt" label="堤防型式" width="120">
-        <template #default="scope">{{ dikePattMap[scope.row.dikePatt] || scope.row.dikePatt }}</template>
+        <template #default="s">{{ dikePattMap[s.row.dikePatt] || s.row.dikePatt }}</template>
       </el-table-column>
-      <el-table-column label="操作" width="100" fixed="right" align="center">
-        <template #default="scope">
-          <el-button type="primary" link icon="View" @click="handleDetail(scope.row)">查看</el-button>
+      <el-table-column label="操作" width="180" fixed="right" align="center">
+        <template #default="s">
+          <el-button type="primary" link icon="View" @click="handleDetail(s.row)">查看</el-button>
+          <el-button type="warning" link icon="Edit" @click="handleUpdate(s.row)">修改</el-button>
+          <el-button type="danger" link icon="Delete" @click="handleDelete(s.row)">删除</el-button>
         </template>
       </el-table-column>
     </el-table>
 
-    <!-- 分页 -->
     <div class="gc-pagination">
       <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
     </div>
 
     <!-- 查看详情弹窗 -->
-    <el-dialog :title="form.dikeName" v-model="open" width="720px" append-to-body>
-      <el-form :model="form" label-width="110px" :disabled="true">
-        <el-row>
-          <el-col :span="12"><el-form-item label="堤防编码">{{ form.dikeCode }}</el-form-item></el-col>
-          <el-col :span="12"><el-form-item label="堤防名称">{{ form.dikeName }}</el-form-item></el-col>
+    <el-dialog :title="form.dikeName" v-model="open" width="900px" append-to-body class="gc-detail-dialog">
+      <el-descriptions :column="3" border>
+        <el-descriptions-item label="堤防名称">{{ form.dikeName }}</el-descriptions-item>
+        <el-descriptions-item label="堤防代码">{{ form.dikeCode }}</el-descriptions-item>
+        <el-descriptions-item label="堤防级别">{{ gradeMap[form.dikeGrad] }}</el-descriptions-item>
+        <el-descriptions-item label="起点所在位置" :span="2">{{ form.startLoc }}</el-descriptions-item>
+        <el-descriptions-item label="终点所在位置">{{ form.endLoc }}</el-descriptions-item>
+        <el-descriptions-item label="堤防类型">{{ form.dikeType }}</el-descriptions-item>
+        <el-descriptions-item label="堤防型式">{{ dikePattMap[form.dikePatt] }}</el-descriptions-item>
+        <el-descriptions-item label="堤防长度">{{ form.dikeLen }} m</el-descriptions-item>
+        <el-descriptions-item label="堤防起点桩号">{{ form.dikeStartNum }}</el-descriptions-item>
+        <el-descriptions-item label="堤防终点桩号">{{ form.dikeEndNum }}</el-descriptions-item>
+        <el-descriptions-item label="起点堤顶高程">{{ form.startDikeTopEl }} m</el-descriptions-item>
+        <el-descriptions-item label="终点堤顶高程">{{ form.endDikeTopEl }} m</el-descriptions-item>
+        <el-descriptions-item label="堤防最小高度">{{ form.dikeHeigMin }} m</el-descriptions-item>
+        <el-descriptions-item label="堤防最大高度">{{ form.dikeHeigMax }} m</el-descriptions-item>
+        <el-descriptions-item label="堤顶最小宽度">{{ form.dikeTopWidMin }} m</el-descriptions-item>
+        <el-descriptions-item label="堤顶最大宽度">{{ form.dikeTopWidMax }} m</el-descriptions-item>
+        <el-descriptions-item label="工程任务">{{ form.engTask }}</el-descriptions-item>
+        <el-descriptions-item label="工程建设情况">{{ engStatMap[form.engStat] }}</el-descriptions-item>
+        <el-descriptions-item label="开工时间">{{ form.startDate }}</el-descriptions-item>
+        <el-descriptions-item label="建成时间">{{ form.compDate }}</el-descriptions-item>
+        <el-descriptions-item label="归口管理部门">{{ form.admDep }}</el-descriptions-item>
+        <el-descriptions-item label="运行状况">{{ runStatMap[form.runStat] }}</el-descriptions-item>
+        <el-descriptions-item label="属性采集时间">{{ form.collDate }}</el-descriptions-item>
+        <el-descriptions-item label="属性更新时间">{{ form.updDate }}</el-descriptions-item>
+        <el-descriptions-item label="起点经度">{{ form.startLong }}</el-descriptions-item>
+        <el-descriptions-item label="起点纬度">{{ form.startLat }}</el-descriptions-item>
+        <el-descriptions-item label="终点经度">{{ form.endLong }}</el-descriptions-item>
+        <el-descriptions-item label="终点纬度">{{ form.endLat }}</el-descriptions-item>
+        <el-descriptions-item label="备注" :span="3">{{ form.note }}</el-descriptions-item>
+      </el-descriptions>
+    </el-dialog>
+
+    <!-- 新增/编辑弹窗 -->
+    <el-dialog :title="dialogTitle" v-model="addEditOpen" width="900px" append-to-body>
+      <el-form ref="addEditFormRef" :model="addEditForm" :rules="addEditRules" label-width="130px" class="gc-edit-form">
+        <el-row :gutter="16">
+          <el-col :span="8"><el-form-item label="堤防名称" prop="dikeName"><el-input v-model="addEditForm.dikeName" maxlength="50" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="堤防代码" prop="dikeCode"><el-input v-model="addEditForm.dikeCode" maxlength="50" :disabled="isEdit" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="堤防级别"><el-select v-model="addEditForm.dikeGrad" placeholder="请选择" style="width:100%"><el-option v-for="(v,k) in gradeMap" :key="k" :label="v" :value="k" /></el-select></el-form-item></el-col>
+        </el-row>
+        <el-row :gutter="16">
+          <el-col :span="8"><el-form-item label="起点所在位置"><el-input v-model="addEditForm.startLoc" maxlength="50" /></el-form-item></el-col>
+          <el-col :span="16"><el-form-item label="终点所在位置"><el-input v-model="addEditForm.endLoc" maxlength="50" /></el-form-item></el-col>
+        </el-row>
+        <el-row :gutter="16">
+          <el-col :span="8"><el-form-item label="堤防类型"><el-select v-model="addEditForm.dikeType" placeholder="请选择" style="width:100%"><el-option label="江河堤防" value="1" /><el-option label="湖堤" value="2" /><el-option label="海堤" value="3" /><el-option label="围堤" value="4" /><el-option label="其他" value="9" /></el-select></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="堤防型式"><el-select v-model="addEditForm.dikePatt" placeholder="请选择" style="width:100%"><el-option v-for="(v,k) in dikePattMap" :key="k" :label="v" :value="k" /></el-select></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="堤防长度"><el-input-number v-model="addEditForm.dikeLen" :precision="2" :controls="false" style="width:100%" /></el-form-item></el-col>
+        </el-row>
+        <el-row :gutter="16">
+          <el-col :span="8"><el-form-item label="堤防起点桩号"><el-input v-model="addEditForm.dikeStartNum" maxlength="50" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="堤防终点桩号"><el-input v-model="addEditForm.dikeEndNum" maxlength="50" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="起点经度"><el-input-number v-model="addEditForm.startLong" :precision="4" :controls="false" style="width:100%" /></el-form-item></el-col>
         </el-row>
-        <el-row>
-          <el-col :span="12"><el-form-item label="起始位置">{{ form.startLoc }}</el-form-item></el-col>
-          <el-col :span="12"><el-form-item label="终点位置">{{ form.endLoc }}</el-form-item></el-col>
+        <el-row :gutter="16">
+          <el-col :span="8"><el-form-item label="起点纬度"><el-input-number v-model="addEditForm.startLat" :precision="4" :controls="false" style="width:100%" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="终点经度"><el-input-number v-model="addEditForm.endLong" :precision="4" :controls="false" style="width:100%" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="终点纬度"><el-input-number v-model="addEditForm.endLat" :precision="4" :controls="false" style="width:100%" /></el-form-item></el-col>
         </el-row>
-        <el-row>
-          <el-col :span="12"><el-form-item label="长度(km)">{{ form.dikeLen }}</el-form-item></el-col>
-          <el-col :span="12"><el-form-item label="等级">{{ gradeMap[form.dikeGrad] }}</el-form-item></el-col>
+        <el-row :gutter="16">
+          <el-col :span="8"><el-form-item label="起点堤顶高程"><el-input-number v-model="addEditForm.startDikeTopEl" :precision="2" :controls="false" style="width:100%" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="终点堤顶高程"><el-input-number v-model="addEditForm.endDikeTopEl" :precision="2" :controls="false" style="width:100%" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="最小堤高"><el-input-number v-model="addEditForm.dikeHeigMin" :precision="2" :controls="false" style="width:100%" /></el-form-item></el-col>
         </el-row>
-        <el-row>
-          <el-col :span="12"><el-form-item label="堤防型式">{{ dikePattMap[form.dikePatt] }}</el-form-item></el-col>
-          <el-col :span="12"><el-form-item label="堤防类型">{{ form.dikeType }}</el-form-item></el-col>
+        <el-row :gutter="16">
+          <el-col :span="8"><el-form-item label="最大堤高"><el-input-number v-model="addEditForm.dikeHeigMax" :precision="2" :controls="false" style="width:100%" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="最小堤顶宽"><el-input-number v-model="addEditForm.dikeTopWidMin" :precision="2" :controls="false" style="width:100%" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="最大堤顶宽"><el-input-number v-model="addEditForm.dikeTopWidMax" :precision="2" :controls="false" style="width:100%" /></el-form-item></el-col>
         </el-row>
-        <el-row>
-          <el-col :span="12"><el-form-item label="运行状况">{{ runStatMap[form.runStat] }}</el-form-item></el-col>
+        <el-row :gutter="16">
+          <el-col :span="8"><el-form-item label="工程建设情况"><el-select v-model="addEditForm.engStat" placeholder="请选择" style="width:100%"><el-option v-for="(v,k) in engStatMap" :key="k" :label="v" :value="k" /></el-select></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="开工时间"><el-date-picker v-model="addEditForm.startDate" type="date" value-format="YYYY-MM-DD" placeholder="请选择" style="width:100%" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="建成时间"><el-date-picker v-model="addEditForm.compDate" type="date" value-format="YYYY-MM-DD" placeholder="请选择" style="width:100%" /></el-form-item></el-col>
+        </el-row>
+        <el-row :gutter="16">
+          <el-col :span="8"><el-form-item label="归口管理部门"><el-input v-model="addEditForm.admDep" maxlength="50" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="运行状况"><el-select v-model="addEditForm.runStat" placeholder="请选择" style="width:100%"><el-option v-for="(v,k) in runStatMap" :key="k" :label="v" :value="k" /></el-select></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="工程任务"><el-input v-model="addEditForm.engTask" maxlength="50" /></el-form-item></el-col>
+        </el-row>
+        <el-row :gutter="16">
+          <el-col :span="24"><el-form-item label="备注"><el-input v-model="addEditForm.note" maxlength="200" type="textarea" :rows="2" /></el-form-item></el-col>
         </el-row>
       </el-form>
+      <template #footer>
+        <el-button @click="addEditOpen = false">取 消</el-button>
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+      </template>
     </el-dialog>
 
-    <!-- 统计弹窗 -->
     <el-dialog title="统计类别" v-model="statsVisible" width="500px" append-to-body>
       <el-table :data="statsData" stripe v-loading="statsLoading">
-        <el-table-column prop="name" label="类别" min-width="120" />
-        <el-table-column prop="value" label="数量" width="120" />
+        <el-table-column prop="name" label="类别" min-width="120"><template #default="s">{{ s.row.NAME || s.row.name }}</template></el-table-column>
+        <el-table-column prop="value" label="数量" width="120"><template #default="s">{{ s.row.VALUE ?? s.row.value }}</template></el-table-column>
       </el-table>
     </el-dialog>
+
+    <excel-import-dialog ref="importRef" title="堤防信息导入" action="/slgc/dike/importData" template-action="/slgc/dike/importTemplate" template-file-name="dike_template" @success="getList" />
   </div>
 </template>
 
 <script setup name="Dike">
 import { getDikeList } from '@/api/slgc/gc/index'
-import { getDike, getDikeCountType } from '@/api/slgc/dike/index'
+import { getDike, getDikeCountType, addDike, updateDike, delDike, exportDike } from '@/api/slgc/dike/index'
+import ExcelImportDialog from '@/components/ExcelImportDialog/index.vue'
 
 const { proxy } = getCurrentInstance()
 const loading = ref(true)
 const total = ref(0)
 const open = ref(false)
+const addEditOpen = ref(false)
 const list = ref([])
+const ids = ref([])
+const single = ref(true)
+const multiple = ref(true)
 const queryParams = reactive({ pageNum: 1, pageSize: 10, dikeName: undefined, startLoc: undefined, dikePatt: undefined })
 const form = ref({})
+const addEditForm = ref({})
+const addEditFormRef = ref(null)
+const importRef = ref(null)
+const isEdit = ref(false)
+const dialogTitle = ref('')
 const statsVisible = ref(false)
 const statsLoading = ref(false)
 const statsData = ref([])
 const gradeMap = { '1': '一级', '2': '二级', '3': '三级', '4': '四级', '5': '五级' }
 const dikePattMap = { '1': '土堤', '2': '砌石堤', '3': '土石混合堤', '4': '钢筋混凝土防洪墙', '9': '其他' }
+const engStatMap = { '1': '已建', '2': '在建', '3': '规划' }
 const runStatMap = { '1': '在用良好', '2': '在用故障', '3': '停用' }
+const addEditRules = {
+  dikeName: [{ required: true, message: '请输入堤防名称', trigger: 'blur' }],
+  dikeCode: [{ required: true, message: '请输入堤防代码', trigger: 'blur' }]
+}
 
 function getList() {
   loading.value = true
-  getDikeList(queryParams).then(res => {
-    list.value = res.rows; total.value = res.total; loading.value = false
-  })
+  getDikeList(queryParams).then(res => { list.value = res.rows; total.value = res.total; loading.value = false })
 }
 function handleQuery() { queryParams.pageNum = 1; getList() }
-function handleDetail(row) {
-  getDike(row.dikeCode).then(res => { form.value = res.data; open.value = true })
+function handleSelectionChange(selection) {
+  ids.value = selection.map(item => item.dikeCode)
+  single.value = selection.length !== 1
+  multiple.value = !selection.length
+}
+function handleDetail(row) { getDike(row.dikeCode).then(res => { form.value = res.data; open.value = true }) }
+function handleAdd() { isEdit.value = false; dialogTitle.value = '新增堤防信息'; addEditForm.value = {}; addEditOpen.value = true }
+function handleUpdate(row) {
+  isEdit.value = true; dialogTitle.value = '修改堤防信息'
+  getDike(row.dikeCode || ids.value[0]).then(res => { addEditForm.value = res.data; addEditOpen.value = true })
+}
+function submitForm() {
+  addEditFormRef.value.validate(valid => {
+    if (!valid) return
+    const fn = isEdit.value ? updateDike : addDike
+    fn(addEditForm.value).then(() => { proxy.$modal.msgSuccess(isEdit.value ? '修改成功' : '新增成功'); addEditOpen.value = false; getList() })
+  })
+}
+function handleDelete(row) {
+  proxy.$modal.confirm('是否确认删除该条堤防数据?').then(() => {
+    return delDike(row.dikeCode)
+  }).then(() => { getList(); proxy.$modal.msgSuccess('删除成功') }).catch(() => {})
+}
+function handleBatchDelete() {
+  if (!ids.value.length) return
+  proxy.$modal.confirm('是否确认删除选中的 ' + ids.value.length + ' 条堤防数据?').then(() => {
+    return delDike(ids.value.join(','))
+  }).then(() => { getList(); proxy.$modal.msgSuccess('删除成功') }).catch(() => {})
+}
+function handleExport() {
+  exportDike(queryParams).then(res => {
+    const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
+    const url = window.URL.createObjectURL(blob)
+    const link = document.createElement('a')
+    link.href = url
+    link.download = '堤防信息数据.xlsx'
+    link.click()
+    window.URL.revokeObjectURL(url)
+  })
 }
 function handleStats() {
-  statsVisible.value = true
-  statsLoading.value = true
-  getDikeCountType().then(res => {
-    statsData.value = res.data || []
-    statsLoading.value = false
-  }).catch(() => { statsLoading.value = false })
+  statsVisible.value = true; statsLoading.value = true
+  getDikeCountType().then(res => { statsData.value = res.data || []; statsLoading.value = false }).catch(() => { statsLoading.value = false })
 }
 
 onMounted(() => { getList() })
 </script>
 
 <style scoped>
-.gc-page {
-  padding: 16px;
-  background: #fff;
-  min-height: calc(100vh - 60px);
-  display: flex;
-  flex-direction: column;
-}
-.gc-toolbar {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  margin-bottom: 16px;
-}
-.gc-toolbar-right {
-  display: flex;
-  align-items: center;
-  gap: 8px;
-}
-.gc-pagination {
-  margin-top: auto;
-  padding-top: 16px;
-  display: flex;
-  justify-content: flex-end;
-}
+.gc-page { padding: 16px; background: #fff; min-height: calc(100vh - 60px); display: flex; flex-direction: column; }
+.gc-toolbar { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; flex-wrap: wrap; gap: 8px; }
+.gc-toolbar-left, .gc-toolbar-right { display: flex; align-items: center; gap: 8px; }
+.gc-pagination { margin-top: auto; padding-top: 16px; display: flex; justify-content: flex-end; }
+:deep(.gc-detail-dialog .el-descriptions__label) { font-weight: 500; color: #303133; background-color: #f5f7fa; }
+:deep(.gc-detail-dialog .el-descriptions__content) { color: #606266; }
+.gc-edit-form :deep(.el-form-item) { margin-bottom: 18px; }
 </style>

+ 99 - 32
gw-ui/src/views/slgc/doc/index.vue

@@ -4,7 +4,7 @@
       <el-col :span="6">
         <el-card shadow="never" class="cate-card">
           <template #header><div class="card-header"><span>文档分类</span><el-button size="small" circle @click="handleAddCate" v-hasRole="['admin']">+</el-button></div></template>
-          <el-tree :data="cateTree" :props="{label:'cateName'}" default-expand-all highlight-current
+          <el-tree ref="cateTreeRef" :data="cateTree" :props="{label:'cateName'}" node-key="cateId" default-expand-all highlight-current
             @node-click="handleCateClick"
             :expand-on-click-node="false">
             <template #default="{ node, data }">
@@ -41,10 +41,9 @@
               <template #default="s">{{ formatDate(s.row.fileTime) }}</template>
             </el-table-column>
             <el-table-column prop="pubUser" label="上传人" width="100" />
-            <el-table-column label="操作" width="280" fixed="right">
+            <el-table-column label="操作" width="230" fixed="right">
               <template #default="s">
                 <div class="op-group">
-                  <el-button type="success" link @click="handleView(s.row)">查看</el-button>
                   <el-button type="primary" link @click="handleDownload(s.row)">下载</el-button>
                   <el-button type="warning" link @click="handleRename(s.row)" v-hasRole="['admin']">重命名</el-button>
                   <el-button type="warning" link @click="handleMove(s.row)" v-hasRole="['admin']">移动</el-button>
@@ -71,23 +70,35 @@
       </el-col>
     </el-row>
     <!-- 上传文档对话框 -->
-    <el-dialog v-model="uploadVisible" title="上传文档" width="500px">
-      <el-form :model="uploadForm" label-width="80px">
-        <el-form-item label="文档描述"><el-input v-model="uploadForm.fileDesc" /></el-form-item>
+    <el-dialog v-model="uploadVisible" title="上传文档" width="400px" append-to-body>
+      <el-form label-width="80px">
+        <el-form-item label="所属分类">
+          <el-tree-select v-model="uploadForm.cateId" :data="cateTree" :props="{ label: 'cateName', value: 'cateId', children: 'children' }" check-strictly placeholder="请选择分类" style="width:100%" clearable />
+        </el-form-item>
+        <el-form-item label="文档描述"><el-input v-model="uploadForm.fileDesc" placeholder="请输入" /></el-form-item>
         <el-form-item label="选择文件">
           <el-upload
             ref="uploadRef"
             :action="uploadUrl"
-            :data="{ fileCate: currentCateId || '0', fileDesc: uploadForm.fileDesc }"
-            :show-file-list="false"
-            :before-upload="beforeUpload"
+            :data="{ fileCate: uploadForm.cateId || '0', fileDesc: uploadForm.fileDesc }"
+            :headers="uploadHeaders"
+            :limit="1"
             :on-success="handleUploadSuccess"
-            :on-error="handleUploadError">
-            <el-button>选择文件</el-button>
+            :on-error="handleUploadError"
+            :on-change="handleUploadFileChange"
+            :on-remove="handleUploadFileRemove"
+            :before-upload="beforeUpload"
+            :auto-upload="false"
+            :show-file-list="true">
+            <el-button type="primary" size="small" icon="Upload">选择文件</el-button>
+            <template #tip><div class="el-upload__tip">支持PDF、Word等常见文件</div></template>
           </el-upload>
         </el-form-item>
       </el-form>
-      <template #footer><el-button @click="uploadVisible=false">关 闭</el-button></template>
+      <template #footer>
+        <el-button type="primary" :loading="isUploading" @click="submitUpload">{{ isUploading ? '上传中...' : '上 传' }}</el-button>
+        <el-button @click="uploadVisible=false">取 消</el-button>
+      </template>
     </el-dialog>
     <!-- 移动文件对话框 -->
     <el-dialog v-model="moveVisible" title="移动到指定分类" width="400px">
@@ -113,12 +124,17 @@
   </div>
 </template>
 <script setup name="Doc">
-import { getDocCateTree, getDocCate, getDocFileList, addDocCate, updateDocCate, delDocCate, delDocFile, moveDocFile, updateDocFile, batchDownloadDoc } from '@/api/slgc/doc/index'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { getDocCateTree, getDocCate, getDocFileList, addDocCate, updateDocCate, delDocCate, delDocFile, moveDocFile, renameDocFile, batchDownloadDoc } from '@/api/slgc/doc/index'
+import { getToken } from '@/utils/auth'
+import request from '@/utils/request'
 const { proxy } = getCurrentInstance()
-const loading = ref(true); const cateTree = ref([]); const fileList = ref([]); const uploadVisible = ref(false)
+const loading = ref(true); const cateTree = ref([]); const cateTreeRef = ref(null); const fileList = ref([]); const uploadVisible = ref(false)
 const uploadRef = ref(null)
-const uploadForm = ref({ fileDesc: '' })
-const uploadUrl = ref('/slgc/doc-file/upload')
+const uploadForm = ref({ fileDesc: '', cateId: '' })
+const uploadHeaders = { Authorization: 'Bearer ' + getToken() }
+const isUploading = ref(false)
+const uploadUrl = ref(import.meta.env.VITE_SERVICE_BASE_TITLE + '/slgc/doc-file/upload')
 const currentCateId = ref('')
 const searchFileName = ref('')
 const selectedRows = ref([])
@@ -156,33 +172,70 @@ function handleEditCate(data) {
 function submitCateEdit() {
   if (!cateEditForm.value.cateName) { ElMessage.warning('请输入分类名称'); return }
   const action = cateEditForm.value.cateId ? updateDocCate(cateEditForm.value) : addDocCate(cateEditForm.value)
-  action.then(() => { ElMessage.success('操作成功'); cateEditVisible.value = false; loadCateTree() })
+  action.then(() => { ElMessage.success('操作成功'); cateEditVisible.value = false; loadCateTree() }).catch(() => { ElMessage.error('操作失败') })
 }
 function handleDeleteCate(data) {
   proxy.$modal.confirm('确认删除分类"' + data.cateName + '"?').then(() => {
-    delDocCate(data.cateId).then(() => { ElMessage.success('删除成功'); loadCateTree() })
-  })
+    delDocCate(data.cateId).then(() => {
+      ElMessage.success('删除成功')
+      loadCateTree()
+      // 删除的是当前选中分类时,重置并刷新列表
+      if (currentCateId.value === data.cateId) {
+        currentCateId.value = ''
+        cateTreeRef.value?.setCurrentKey('')
+        loadFileList()
+      }
+    }).catch(() => { ElMessage.error('删除失败') })
+  }).catch(() => {})
 }
-function handleUpload() { uploadVisible.value = true }
+function handleUpload() { uploadVisible.value = true; uploadForm.value = { fileDesc: '', cateId: currentCateId.value || '' }; isUploading.value = false; uploadRef.value?.clearFiles() }
 function beforeUpload(file) {
   const maxSize = 50 * 1024 * 1024
   if (file.size > maxSize) { ElMessage.error('文件大小不能超过 50MB'); return false }
   return true
 }
+function handleUploadFileChange(file) {
+  if (!uploadForm.value.fileDesc) uploadForm.value.fileDesc = file.name || file.raw?.name || ''
+}
+function handleUploadFileRemove() { }
 function handleUploadSuccess(res) {
-  if (res.code === 200) { ElMessage.success('上传成功'); loadFileList() }
+  isUploading.value = false
+  uploadVisible.value = false
+  if (res.code === 200) {
+    ElMessage.success('上传成功')
+    // 切换到上传所选分类并刷新列表(含左侧树高亮)
+    if (uploadForm.value.cateId && uploadForm.value.cateId !== currentCateId.value) {
+      currentCateId.value = uploadForm.value.cateId
+      cateTreeRef.value?.setCurrentKey(uploadForm.value.cateId)
+    }
+    loadFileList()
+  }
   else { ElMessage.error(res.msg || '上传失败') }
 }
-function handleUploadError() { ElMessage.error('上传失败') }
+function handleUploadError() { isUploading.value = false; ElMessage.error('上传失败') }
 function handleSelectionChange(rows) { selectedRows.value = rows }
-function handleView(row) {
-  window.open(`/common/download/resource?resource=${encodeURIComponent(row.filePath)}`, '_blank')
+function downloadFile(row) {
+  if (!row.filePath) return
+  request({ url: '/common/download/resource', method: 'get', params: { resource: row.filePath }, responseType: 'blob' }).then(res => {
+    const blob = new Blob([res])
+    const url = window.URL.createObjectURL(blob)
+    const link = document.createElement('a')
+    link.href = url
+    link.download = row.fileName || '下载文件'
+    link.click()
+    window.URL.revokeObjectURL(url)
+  }).catch(() => { ElMessage.error('下载失败') })
 }
-function handleDownload(row) {
-  const url = `/common/download/resource?resource=${encodeURIComponent(row.filePath)}`
-  const a = document.createElement('a')
-  a.href = url; a.download = row.fileName; a.click()
+function submitUpload() {
+  if (!uploadRef.value) return
+  isUploading.value = true
+  try {
+    uploadRef.value.submit()
+  } catch (e) {
+    isUploading.value = false
+  }
 }
+function handleDownload(row) { downloadFile(row) }
 
 // 批量下载:后端打包ZIP一次性下载
 function handleBatchDownload() {
@@ -218,10 +271,10 @@ function handleRename(row) {
     cancelButtonText: '取消',
     inputValidator: (val) => val && val.trim() ? true : '文件名不能为空',
   }).then(({ value }) => {
-    updateDocFile({ fileId: row.fileId, fileName: value.trim() }).then(() => {
+    renameDocFile(row.fileId, value.trim()).then(() => {
       ElMessage.success('重命名成功')
       loadFileList()
-    })
+    }).catch(() => { ElMessage.error('重命名失败') })
   }).catch(() => {})
 }
 function handleMove(row) {
@@ -234,11 +287,25 @@ function handleMoveCateClick(data) { moveTargetCateId.value = data.cateId }
 function submitMove() {
   if (!moveTargetCateId.value) { ElMessage.warning('请选择目标分类'); return }
   moveDocFile({ fileIds: moveFileIds.value.join(','), fileCate: moveTargetCateId.value }).then(() => {
-    ElMessage.success('移动成功'); moveVisible.value = false; loadFileList()
+    ElMessage.success('移动成功')
+    moveVisible.value = false
+    loadFileList()
+  }).catch(() => {
+    ElMessage.error('移动失败')
+    moveVisible.value = false
   })
 }
 function handleDelete(row) {
-  proxy.$modal.confirm('确认删除"' + row.fileName + '"?').then(() => { delDocFile(row.fileId).then(() => { ElMessage.success('删除成功'); loadFileList() }) })
+  proxy.$modal.confirm('确认删除"' + row.fileName + '"?').then(() => {
+    delDocFile(row.fileId).then(() => {
+      ElMessage.success('删除成功')
+      // 删除的是当前页最后一条且非第一页时,回退一页
+      if (fileList.value.length === 1 && pageNum.value > 1) {
+        pageNum.value--
+      }
+      loadFileList()
+    }).catch(() => { ElMessage.error('删除失败') })
+  }).catch(() => {})
 }
 function loadFileList() {
   const params = { pageNum: pageNum.value, pageSize: pageSize.value }

+ 1 - 0
gw-ui/src/views/slgc/earlywarning/index.vue

@@ -42,6 +42,7 @@
   </div>
 </template>
 <script setup name="EarlyWarning">
+import { ElMessage, ElMessageBox } from 'element-plus'
 import { getWarningList, getWarningHistory } from '@/api/slgc/earlywarning/index'
 const loading = ref(true); const warnings = ref([]); const history = ref([])
 const hq = reactive({ station: '', type: '' })

+ 22 - 4
gw-ui/src/views/slgc/law/index.vue

@@ -154,14 +154,16 @@
   </div>
 </template>
 <script setup name="Law">
+import { ElMessage, ElMessageBox } from 'element-plus'
 import { getLawCateList, getLawCateTree, getLawCate, addLawCate, updateLawCate, delLawCate, getLawList, getLawDetail, saveLaw, delLaw, exportLaw, importTemplateUrl } from '@/api/slgc/law/index'
 import { getToken } from '@/utils/auth'
+import request from '@/utils/request'
 import Editor from '@/components/Editor/index'
 const { proxy } = getCurrentInstance()
 // 导入导出
 const importVisible = ref(false)
 const importUploadRef = ref(null)
-const importUrl = ref('/slgc/cms-article/importData')
+const importUrl = ref(import.meta.env.VITE_SERVICE_BASE_TITLE + '/slgc/cms-article/importData')
 const importCateId = ref('')
 const uploadHeaders = ref({ Authorization: 'Bearer ' + getToken() })
 const loading = ref(true); const open = ref(false); const title = ref('')
@@ -195,7 +197,15 @@ function handleExport() {
   })
 }
 function handleDownloadTemplate() {
-  window.open(importTemplateUrl(), '_blank')
+  request({ url: importTemplateUrl(), method: 'get', responseType: 'blob' }).then(res => {
+    const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
+    const url = window.URL.createObjectURL(blob)
+    const link = document.createElement('a')
+    link.href = url
+    link.download = '法律法规导入模板.xlsx'
+    link.click()
+    window.URL.revokeObjectURL(url)
+  })
 }
 function handleImportSuccess(res) {
   if (res.code === 200) {
@@ -267,10 +277,18 @@ function submitCateForm() {
   const action = cateForm.value.cateId ? updateLawCate(cateForm.value) : addLawCate(cateForm.value)
   action.then(() => { ElMessage.success('操作成功'); cateOpen.value = false; loadCateTree() })
 }
-function loadCateTree() { getLawCateTree().then(r => { cateTree.value = r.data || r.rows || [] }) }
+function loadCateTree() {
+  getLawCateTree().then(r => {
+    cateTree.value = r.data || r.rows || []
+    // 默认选中第一个分类,保证左右联动
+    if (cateTree.value.length > 0) {
+      currentCateId.value = cateTree.value[0].cateId
+    }
+    loadLawList()
+  })
+}
 onMounted(() => {
   loadCateTree()
-  loadLawList()
 })
 </script>
 <style scoped>

+ 142 - 73
gw-ui/src/views/slgc/pust/index.vue

@@ -1,26 +1,34 @@
-<template>
+<template>
   <div class="gc-page">
     <div class="gc-toolbar">
-      <el-button type="success" icon="DataAnalysis" @click="handleStats">统计</el-button>
+      <div class="gc-toolbar-left">
+        <el-button type="success" icon="DataAnalysis" @click="handleStats">统计</el-button>
+        <el-button type="primary" icon="Plus" @click="handleAdd">新增</el-button>
+        <el-button type="warning" icon="Edit" :disabled="single" @click="handleUpdate">修改</el-button>
+        <el-button type="danger" icon="Delete" :disabled="multiple" @click="handleBatchDelete">删除</el-button>
+        <el-button type="info" icon="Upload" @click="importRef.open()">导入</el-button>
+        <el-button type="success" icon="Download" @click="handleExport">导出</el-button>
+      </div>
       <div class="gc-toolbar-right">
         <el-input v-model="queryParams.pustName" placeholder="请输入名称" clearable style="width: 160px" @keyup.enter="handleQuery" />
         <el-input v-model="queryParams.pustLoc" placeholder="请输入位置" clearable style="width: 160px" @keyup.enter="handleQuery" />
         <el-select v-model="queryParams.pustType" placeholder="请选择类别-" clearable style="width: 140px">
-          <el-option label="排水泵站" value="1" />
-          <el-option label="供水泵站" value="2" />
-          <el-option label="供排结合泵站" value="3" />
+          <el-option label="排水泵站" value="1" /><el-option label="供水泵站" value="2" /><el-option label="供排结合泵站" value="3" />
         </el-select>
         <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
       </div>
     </div>
 
-    <el-table v-loading="loading" :data="list" stripe border>
+    <el-table v-loading="loading" :data="list" stripe border style="width: 100%" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
       <el-table-column prop="pustName" label="泵站名称" min-width="180" />
       <el-table-column prop="pustLoc" label="泵站所在位置" min-width="220" />
       <el-table-column prop="insFlow" label="装机流量(m³/s)" width="150" />
-      <el-table-column label="操作" width="100" fixed="right" align="center">
+      <el-table-column label="操作" width="180" fixed="right" align="center">
         <template #default="s">
           <el-button type="primary" link icon="View" @click="handleDetail(s.row)">查看</el-button>
+          <el-button type="warning" link icon="Edit" @click="handleUpdate(s.row)">修改</el-button>
+          <el-button type="danger" link icon="Delete" @click="handleDelete(s.row)">删除</el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -30,114 +38,175 @@
     </div>
 
     <!-- 查看详情弹窗 -->
-    <el-dialog :title="form.pustName" v-model="open" width="720px" append-to-body>
-      <el-form :model="form" label-width="130px" :disabled="true">
-        <el-row>
-          <el-col :span="12"><el-form-item label="泵站名称">{{ form.pustName }}</el-form-item></el-col>
-          <el-col :span="12"><el-form-item label="泵站代码">{{ form.pustCode }}</el-form-item></el-col>
-        </el-row>
-        <el-row>
-          <el-col :span="12"><el-form-item label="泵站经度">{{ form.pustLon }}</el-form-item></el-col>
-          <el-col :span="12"><el-form-item label="泵站纬度">{{ form.pustLat }}</el-form-item></el-col>
+    <el-dialog :title="form.pustName" v-model="open" width="900px" append-to-body class="gc-detail-dialog">
+      <el-descriptions :column="3" border>
+        <el-descriptions-item label="泵站名称">{{ form.pustName }}</el-descriptions-item>
+        <el-descriptions-item label="泵站代码">{{ form.pustCode }}</el-descriptions-item>
+        <el-descriptions-item label="泵站经度">{{ form.pustLong }}</el-descriptions-item>
+        <el-descriptions-item label="泵站纬度">{{ form.pustLat }}</el-descriptions-item>
+        <el-descriptions-item label="泵站所在位置" :span="2">{{ form.pustLoc }}</el-descriptions-item>
+        <el-descriptions-item label="泵站类型">{{ pustTypeMap[form.pustType] }}</el-descriptions-item>
+        <el-descriptions-item label="装机流量">{{ form.insFlow }} m³/s</el-descriptions-item>
+        <el-descriptions-item label="装机功率">{{ form.insPow }}</el-descriptions-item>
+        <el-descriptions-item label="水泵数量">{{ form.pumpNum }}</el-descriptions-item>
+        <el-descriptions-item label="设计扬程">{{ form.desHead }}</el-descriptions-item>
+        <el-descriptions-item label="工程任务">{{ form.engTask }}</el-descriptions-item>
+        <el-descriptions-item label="工程等别">{{ engGradMap[form.engGrad] }}</el-descriptions-item>
+        <el-descriptions-item label="工程规模">{{ form.engScal }}</el-descriptions-item>
+        <el-descriptions-item label="主要建筑物级别">{{ form.mainBuildGrad }}</el-descriptions-item>
+        <el-descriptions-item label="工程建设情况">{{ engStatMap[form.engStat] }}</el-descriptions-item>
+        <el-descriptions-item label="开工时间">{{ form.startDate }}</el-descriptions-item>
+        <el-descriptions-item label="建成时间">{{ form.compDate }}</el-descriptions-item>
+        <el-descriptions-item label="归口管理部门" :span="2">{{ form.admDep }}</el-descriptions-item>
+        <el-descriptions-item label="备注" :span="3">{{ form.note }}</el-descriptions-item>
+      </el-descriptions>
+    </el-dialog>
+
+    <!-- 新增/编辑弹窗 -->
+    <el-dialog :title="dialogTitle" v-model="addEditOpen" width="900px" append-to-body>
+      <el-form ref="addEditFormRef" :model="addEditForm" :rules="addEditRules" label-width="140px" class="gc-edit-form">
+        <el-row :gutter="16">
+          <el-col :span="8"><el-form-item label="泵站名称" prop="pustName"><el-input v-model="addEditForm.pustName" maxlength="50" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="泵站代码" prop="pustCode"><el-input v-model="addEditForm.pustCode" maxlength="50" :disabled="isEdit" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="泵站经度"><el-input-number v-model="addEditForm.pustLong" :precision="4" :controls="false" style="width:100%" /></el-form-item></el-col>
         </el-row>
-        <el-row>
-          <el-col :span="12"><el-form-item label="泵站所在位置">{{ form.pustLoc }}</el-form-item></el-col>
-          <el-col :span="12"><el-form-item label="泵站类型">{{ pustTypeMap[form.pustType] }}</el-form-item></el-col>
+        <el-row :gutter="16">
+          <el-col :span="8"><el-form-item label="泵站纬度"><el-input-number v-model="addEditForm.pustLat" :precision="4" :controls="false" style="width:100%" /></el-form-item></el-col>
+          <el-col :span="16"><el-form-item label="泵站所在位置"><el-input v-model="addEditForm.pustLoc" maxlength="50" /></el-form-item></el-col>
         </el-row>
-        <el-row>
-          <el-col :span="12"><el-form-item label="装机流量">{{ form.insFlow }} m³/s</el-form-item></el-col>
-          <el-col :span="12"><el-form-item label="装机功率">{{ form.insPow }}</el-form-item></el-col>
+        <el-row :gutter="16">
+          <el-col :span="8"><el-form-item label="泵站类型"><el-select v-model="addEditForm.pustType" placeholder="请选择" style="width:100%"><el-option v-for="(v,k) in pustTypeMap" :key="k" :label="v" :value="k" /></el-select></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="装机流量"><el-input-number v-model="addEditForm.insFlow" :precision="2" :controls="false" style="width:100%" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="装机功率"><el-input-number v-model="addEditForm.insPow" :precision="2" :controls="false" style="width:100%" /></el-form-item></el-col>
         </el-row>
-        <el-row>
-          <el-col :span="12"><el-form-item label="水泵数量">{{ form.pumpNum }}</el-form-item></el-col>
-          <el-col :span="12"><el-form-item label="设计扬程">{{ form.designHead }}</el-form-item></el-col>
+        <el-row :gutter="16">
+          <el-col :span="8"><el-form-item label="水泵数量"><el-input-number v-model="addEditForm.pumpNum" :controls="false" style="width:100%" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="设计扬程"><el-input-number v-model="addEditForm.desHead" :precision="2" :controls="false" style="width:100%" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="工程任务"><el-input v-model="addEditForm.engTask" maxlength="50" /></el-form-item></el-col>
         </el-row>
-        <el-row>
-          <el-col :span="12"><el-form-item label="工程等别">{{ engGradMap[form.engGrad] }}</el-form-item></el-col>
-          <el-col :span="12"><el-form-item label="工程规模">{{ form.engScale }}</el-form-item></el-col>
+        <el-row :gutter="16">
+          <el-col :span="8"><el-form-item label="工程等别"><el-select v-model="addEditForm.engGrad" placeholder="请选择" style="width:100%"><el-option v-for="(v,k) in engGradMap" :key="k" :label="v" :value="k" /></el-select></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="工程规模"><el-select v-model="addEditForm.engScal" placeholder="请选择" style="width:100%"><el-option label="大(1)型" value="1" /><el-option label="大(2)型" value="2" /><el-option label="中型" value="3" /><el-option label="小(1)型" value="4" /><el-option label="小(2)型" value="5" /></el-select></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="主要建筑物级别"><el-select v-model="addEditForm.mainBuildGrad" placeholder="请选择" style="width:100%"><el-option label="1级" value="1" /><el-option label="2级" value="2" /><el-option label="3级" value="3" /><el-option label="4级" value="4" /><el-option label="5级" value="5" /></el-select></el-form-item></el-col>
         </el-row>
-        <el-row>
-          <el-col :span="12"><el-form-item label="工程建设情况">{{ form.engBuild }}</el-form-item></el-col>
-          <el-col :span="12"><el-form-item label="开工时间">{{ form.startDate }}</el-form-item></el-col>
+        <el-row :gutter="16">
+          <el-col :span="8"><el-form-item label="工程建设情况"><el-select v-model="addEditForm.engStat" placeholder="请选择" style="width:100%"><el-option v-for="(v,k) in engStatMap" :key="k" :label="v" :value="k" /></el-select></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="开工时间"><el-date-picker v-model="addEditForm.startDate" type="date" value-format="YYYY-MM-DD" placeholder="请选择" style="width:100%" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="建成时间"><el-date-picker v-model="addEditForm.compDate" type="date" value-format="YYYY-MM-DD" placeholder="请选择" style="width:100%" /></el-form-item></el-col>
         </el-row>
-        <el-row>
-          <el-col :span="12"><el-form-item label="建成时间">{{ form.endDate }}</el-form-item></el-col>
-          <el-col :span="12"><el-form-item label="归口管理部门">{{ form.admDep }}</el-form-item></el-col>
+        <el-row :gutter="16">
+          <el-col :span="8"><el-form-item label="归口管理部门"><el-input v-model="addEditForm.admDep" maxlength="50" /></el-form-item></el-col>
+          <el-col :span="16"><el-form-item label="备注"><el-input v-model="addEditForm.note" maxlength="200" type="textarea" :rows="2" /></el-form-item></el-col>
         </el-row>
       </el-form>
+      <template #footer>
+        <el-button @click="addEditOpen = false">取 消</el-button>
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+      </template>
     </el-dialog>
 
-    <!-- 统计弹窗 -->
     <el-dialog title="统计类别" v-model="statsVisible" width="500px" append-to-body>
       <el-table :data="statsData" stripe v-loading="statsLoading">
-        <el-table-column prop="name" label="类别" min-width="120" />
-        <el-table-column prop="value" label="数量" width="120" />
+        <el-table-column prop="name" label="类别" min-width="120"><template #default="s">{{ s.row.NAME || s.row.name }}</template></el-table-column>
+        <el-table-column prop="value" label="数量" width="120"><template #default="s">{{ s.row.VALUE ?? s.row.value }}</template></el-table-column>
       </el-table>
     </el-dialog>
+
+    <excel-import-dialog ref="importRef" title="泵站信息导入" action="/slgc/pust/importData" template-action="/slgc/pust/importTemplate" template-file-name="pust_template" @success="getList" />
   </div>
 </template>
 
 <script setup name="Pust">
 import { getPustList } from '@/api/slgc/gc/index'
-import { getPust, getPustCountType } from '@/api/slgc/pust/index'
+import { getPust, getPustCountType, addPust, updatePust, delPust, exportPust } from '@/api/slgc/pust/index'
+import ExcelImportDialog from '@/components/ExcelImportDialog/index.vue'
 
+const { proxy } = getCurrentInstance()
 const loading = ref(true)
 const total = ref(0)
 const open = ref(false)
+const addEditOpen = ref(false)
 const list = ref([])
+const ids = ref([])
+const single = ref(true)
+const multiple = ref(true)
 const queryParams = reactive({ pageNum: 1, pageSize: 10, pustName: undefined, pustLoc: undefined, pustType: undefined })
 const form = ref({})
+const addEditForm = ref({})
+const addEditFormRef = ref(null)
+const importRef = ref(null)
+const isEdit = ref(false)
+const dialogTitle = ref('')
 const statsVisible = ref(false)
 const statsLoading = ref(false)
 const statsData = ref([])
 const pustTypeMap = { '1': '排水泵站', '2': '供水泵站', '3': '供排结合泵站' }
 const engGradMap = { '1': 'Ⅰ等', '2': 'Ⅱ等', '3': 'Ⅲ等', '4': 'Ⅳ等', '5': 'Ⅴ等' }
+const engStatMap = { '1': '已建', '2': '在建', '3': '规划' }
+const addEditRules = {
+  pustName: [{ required: true, message: '请输入泵站名称', trigger: 'blur' }],
+  pustCode: [{ required: true, message: '请输入泵站代码', trigger: 'blur' }]
+}
 
 function getList() {
   loading.value = true
-  getPustList(queryParams).then(res => {
-    list.value = res.rows; total.value = res.total; loading.value = false
-  })
+  getPustList(queryParams).then(res => { list.value = res.rows; total.value = res.total; loading.value = false })
 }
 function handleQuery() { queryParams.pageNum = 1; getList() }
-function handleDetail(row) {
-  getPust(row.pustCode).then(res => { form.value = res.data; open.value = true })
+function handleSelectionChange(selection) {
+  ids.value = selection.map(item => item.pustCode)
+  single.value = selection.length !== 1
+  multiple.value = !selection.length
+}
+function handleDetail(row) { getPust(row.pustCode).then(res => { form.value = res.data; open.value = true }) }
+function handleAdd() { isEdit.value = false; dialogTitle.value = '新增泵站信息'; addEditForm.value = {}; addEditOpen.value = true }
+function handleUpdate(row) {
+  isEdit.value = true; dialogTitle.value = '修改泵站信息'
+  getPust(row.pustCode || ids.value[0]).then(res => { addEditForm.value = res.data; addEditOpen.value = true })
+}
+function submitForm() {
+  addEditFormRef.value.validate(valid => {
+    if (!valid) return
+    const fn = isEdit.value ? updatePust : addPust
+    fn(addEditForm.value).then(() => { proxy.$modal.msgSuccess(isEdit.value ? '修改成功' : '新增成功'); addEditOpen.value = false; getList() })
+  })
+}
+function handleDelete(row) {
+  proxy.$modal.confirm('是否确认删除该条泵站数据?').then(() => {
+    return delPust(row.pustCode)
+  }).then(() => { getList(); proxy.$modal.msgSuccess('删除成功') }).catch(() => {})
+}
+function handleBatchDelete() {
+  if (!ids.value.length) return
+  proxy.$modal.confirm('是否确认删除选中的 ' + ids.value.length + ' 条泵站数据?').then(() => {
+    return delPust(ids.value.join(','))
+  }).then(() => { getList(); proxy.$modal.msgSuccess('删除成功') }).catch(() => {})
+}
+function handleExport() {
+  exportPust(queryParams).then(res => {
+    const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
+    const url = window.URL.createObjectURL(blob)
+    const link = document.createElement('a')
+    link.href = url
+    link.download = '泵站信息数据.xlsx'
+    link.click()
+    window.URL.revokeObjectURL(url)
+  })
 }
 function handleStats() {
-  statsVisible.value = true
-  statsLoading.value = true
-  getPustCountType().then(res => {
-    statsData.value = res.data || []
-    statsLoading.value = false
-  }).catch(() => { statsLoading.value = false })
+  statsVisible.value = true; statsLoading.value = true
+  getPustCountType().then(res => { statsData.value = res.data || []; statsLoading.value = false }).catch(() => { statsLoading.value = false })
 }
 
 onMounted(() => { getList() })
 </script>
 
 <style scoped>
-.gc-page {
-  padding: 16px;
-  background: #fff;
-  min-height: calc(100vh - 60px);
-  display: flex;
-  flex-direction: column;
-}
-.gc-toolbar {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  margin-bottom: 16px;
-}
-.gc-toolbar-right {
-  display: flex;
-  align-items: center;
-  gap: 8px;
-}
-.gc-pagination {
-  margin-top: auto;
-  padding-top: 16px;
-  display: flex;
-  justify-content: flex-end;
-}
+.gc-page { padding: 16px; background: #fff; min-height: calc(100vh - 60px); display: flex; flex-direction: column; }
+.gc-toolbar { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; flex-wrap: wrap; gap: 8px; }
+.gc-toolbar-left, .gc-toolbar-right { display: flex; align-items: center; gap: 8px; }
+.gc-pagination { margin-top: auto; padding-top: 16px; display: flex; justify-content: flex-end; }
+:deep(.gc-detail-dialog .el-descriptions__label) { font-weight: 500; color: #303133; background-color: #f5f7fa; }
+:deep(.gc-detail-dialog .el-descriptions__content) { color: #606266; }
+.gc-edit-form :deep(.el-form-item) { margin-bottom: 18px; }
 </style>

+ 189 - 29
gw-ui/src/views/slgc/reservoir/index.vue

@@ -1,8 +1,15 @@
-<template>
+<template>
   <div class="gc-page">
     <!-- 顶部工具栏 -->
     <div class="gc-toolbar">
-      <el-button type="success" icon="DataAnalysis" @click="handleStats">统计</el-button>
+      <div class="gc-toolbar-left">
+        <el-button type="success" icon="DataAnalysis" @click="handleStats">统计</el-button>
+        <el-button type="primary" icon="Plus" @click="handleAdd">新增</el-button>
+        <el-button type="warning" icon="Edit" :disabled="single" @click="handleUpdate">修改</el-button>
+        <el-button type="danger" icon="Delete" :disabled="multiple" @click="handleBatchDelete">删除</el-button>
+        <el-button type="info" icon="Upload" @click="importRef.open()">导入</el-button>
+        <el-button type="success" icon="Download" @click="handleExport">导出</el-button>
+      </div>
       <div class="gc-toolbar-right">
         <el-input v-model="queryParams.resName" placeholder="水库名称" clearable style="width: 160px" @keyup.enter="handleQuery" />
         <el-input v-model="queryParams.resLoc" placeholder="水库所在位置" clearable style="width: 160px" @keyup.enter="handleQuery" />
@@ -16,7 +23,8 @@
     </div>
 
     <!-- 数据表格 -->
-    <el-table v-loading="loading" :data="list" stripe border style="width: 100%">
+    <el-table v-loading="loading" :data="list" stripe border style="width: 100%" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
       <el-table-column prop="resName" label="水库名称" min-width="160" />
       <el-table-column prop="resLoc" label="水库所在位置" min-width="200" />
       <el-table-column prop="resType" label="水库类型" width="120">
@@ -26,9 +34,11 @@
         <template #default="scope">{{ engGradMap[scope.row.engGrad] || scope.row.engGrad }}</template>
       </el-table-column>
       <el-table-column prop="totCap" label="总库容(10⁴m³)" width="140" />
-      <el-table-column label="操作" width="100" fixed="right" align="center">
+      <el-table-column label="操作" width="180" fixed="right" align="center">
         <template #default="scope">
           <el-button type="primary" link icon="View" @click="handleDetail(scope.row)">查看</el-button>
+          <el-button type="warning" link icon="Edit" @click="handleUpdate(scope.row)">修改</el-button>
+          <el-button type="danger" link icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -38,63 +48,144 @@
       <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
     </div>
 
-    <!-- 查看详情弹窗 -->
-    <el-dialog :title="form.resName" v-model="open" width="720px" append-to-body>
-      <el-form :model="form" label-width="130px" :disabled="true">
-        <el-row>
-          <el-col :span="12"><el-form-item label="水库编码">{{ form.resCode }}</el-form-item></el-col>
-          <el-col :span="12"><el-form-item label="水库名称">{{ form.resName }}</el-form-item></el-col>
+    <!-- 查看详情弹窗 — el-descriptions 风格 -->
+    <el-dialog :title="form.resName" v-model="open" width="900px" append-to-body class="gc-detail-dialog">
+      <el-descriptions :column="3" border>
+        <el-descriptions-item label="水库名称">{{ form.resName }}</el-descriptions-item>
+        <el-descriptions-item label="水库代码">{{ form.resCode }}</el-descriptions-item>
+        <el-descriptions-item label="左下角经度">{{ form.lowLeftLong }}</el-descriptions-item>
+        <el-descriptions-item label="左下角纬度">{{ form.lowLeftLat }}</el-descriptions-item>
+        <el-descriptions-item label="右上角经度">{{ form.upRightLong }}</el-descriptions-item>
+        <el-descriptions-item label="右上角纬度">{{ form.upRightLat }}</el-descriptions-item>
+        <el-descriptions-item label="水库所在位置" :span="2">{{ form.resLoc }}</el-descriptions-item>
+        <el-descriptions-item label="水库类型">{{ resTypeMap[form.resType] }}</el-descriptions-item>
+        <el-descriptions-item label="工程等别">{{ engGradMap[form.engGrad] }}</el-descriptions-item>
+        <el-descriptions-item label="工程规模">{{ form.engScal }}</el-descriptions-item>
+        <el-descriptions-item label="坝址控制流域面积">{{ form.watShedArea }} km²</el-descriptions-item>
+        <el-descriptions-item label="坝址多年平均径流量">{{ form.daadMulAverRuof }}</el-descriptions-item>
+        <el-descriptions-item label="防洪高水位">{{ form.uppLevFlco }}</el-descriptions-item>
+        <el-descriptions-item label="正常蓄水位">{{ form.normWatLev }} m</el-descriptions-item>
+        <el-descriptions-item label="正常蓄水位相应水面面积">{{ form.normPoolStagArea }}</el-descriptions-item>
+        <el-descriptions-item label="正常蓄水位相应库容">{{ form.normPoolStagCap }}</el-descriptions-item>
+        <el-descriptions-item label="主汛期防洪限制水位">{{ form.flLowLimLev }}</el-descriptions-item>
+        <el-descriptions-item label="防洪限制水位库容">{{ form.flLowLimLevCap }}</el-descriptions-item>
+        <el-descriptions-item label="死水位">{{ form.deadLev }} m</el-descriptions-item>
+        <el-descriptions-item label="总库容">{{ form.totCap }} 10⁴m³</el-descriptions-item>
+        <el-descriptions-item label="调洪库容">{{ form.storFlCap }} 10⁴m³</el-descriptions-item>
+        <el-descriptions-item label="防洪库容">{{ form.flcoCap }} 10⁴m³</el-descriptions-item>
+        <el-descriptions-item label="兴利库容">{{ form.benResCap }} 10⁴m³</el-descriptions-item>
+        <el-descriptions-item label="死库容">{{ form.deadCap }} 10⁴m³</el-descriptions-item>
+        <el-descriptions-item label="工程建设情况">{{ engStatMap[form.engStat] }}</el-descriptions-item>
+        <el-descriptions-item label="开工时间">{{ form.startDate }}</el-descriptions-item>
+        <el-descriptions-item label="建成时间">{{ form.compDate }}</el-descriptions-item>
+        <el-descriptions-item label="归口管理部门">{{ form.admDep }}</el-descriptions-item>
+        <el-descriptions-item label="备注" :span="2">{{ form.note }}</el-descriptions-item>
+      </el-descriptions>
+    </el-dialog>
+
+    <!-- 新增/编辑弹窗 -->
+    <el-dialog :title="dialogTitle" v-model="addEditOpen" width="900px" append-to-body>
+      <el-form ref="addEditFormRef" :model="addEditForm" :rules="addEditRules" label-width="150px" class="gc-edit-form">
+        <el-row :gutter="16">
+          <el-col :span="8"><el-form-item label="水库名称" prop="resName"><el-input v-model="addEditForm.resName" maxlength="50" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="水库代码" prop="resCode"><el-input v-model="addEditForm.resCode" maxlength="50" :disabled="isEdit" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="左下角经度"><el-input-number v-model="addEditForm.lowLeftLong" :precision="4" :controls="false" style="width:100%" /></el-form-item></el-col>
+        </el-row>
+        <el-row :gutter="16">
+          <el-col :span="8"><el-form-item label="左下角纬度"><el-input-number v-model="addEditForm.lowLeftLat" :precision="4" :controls="false" style="width:100%" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="右上角经度"><el-input-number v-model="addEditForm.upRightLong" :precision="4" :controls="false" style="width:100%" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="右上角纬度"><el-input-number v-model="addEditForm.upRightLat" :precision="4" :controls="false" style="width:100%" /></el-form-item></el-col>
+        </el-row>
+        <el-row :gutter="16">
+          <el-col :span="8"><el-form-item label="水库所在位置"><el-input v-model="addEditForm.resLoc" maxlength="50" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="水库类型"><el-select v-model="addEditForm.resType" placeholder="请选择" style="width:100%"><el-option v-for="(v,k) in resTypeMap" :key="k" :label="v" :value="k" /></el-select></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="工程等别"><el-select v-model="addEditForm.engGrad" placeholder="请选择" style="width:100%"><el-option v-for="(v,k) in engGradMap" :key="k" :label="v" :value="k" /></el-select></el-form-item></el-col>
+        </el-row>
+        <el-row :gutter="16">
+          <el-col :span="8"><el-form-item label="工程规模"><el-select v-model="addEditForm.engScal" placeholder="请选择" style="width:100%"><el-option label="大(1)型" value="1" /><el-option label="大(2)型" value="2" /><el-option label="中型" value="3" /><el-option label="小(1)型" value="4" /><el-option label="小(2)型" value="5" /></el-select></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="坝址控制流域面积"><el-input-number v-model="addEditForm.watShedArea" :precision="2" :controls="false" style="width:100%" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="坝址多年平均径流量"><el-input-number v-model="addEditForm.daadMulAverRuof" :precision="2" :controls="false" style="width:100%" /></el-form-item></el-col>
+        </el-row>
+        <el-row :gutter="16">
+          <el-col :span="8"><el-form-item label="防洪高水位"><el-input-number v-model="addEditForm.uppLevFlco" :precision="2" :controls="false" style="width:100%" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="正常蓄水位"><el-input-number v-model="addEditForm.normWatLev" :precision="2" :controls="false" style="width:100%" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="正常蓄水位相应水面面积"><el-input-number v-model="addEditForm.normPoolStagArea" :precision="2" :controls="false" style="width:100%" /></el-form-item></el-col>
         </el-row>
-        <el-row>
-          <el-col :span="12"><el-form-item label="位置">{{ form.resLoc }}</el-form-item></el-col>
-          <el-col :span="12"><el-form-item label="类型">{{ resTypeMap[form.resType] }}</el-form-item></el-col>
+        <el-row :gutter="16">
+          <el-col :span="8"><el-form-item label="正常蓄水位相应库容"><el-input-number v-model="addEditForm.normPoolStagCap" :precision="2" :controls="false" style="width:100%" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="主汛期防洪限制水位"><el-input-number v-model="addEditForm.flLowLimLev" :precision="2" :controls="false" style="width:100%" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="防洪限制水位库容"><el-input-number v-model="addEditForm.flLowLimLevCap" :precision="2" :controls="false" style="width:100%" /></el-form-item></el-col>
         </el-row>
-        <el-row>
-          <el-col :span="12"><el-form-item label="工程等别">{{ engGradMap[form.engGrad] }}</el-form-item></el-col>
-          <el-col :span="12"><el-form-item label="总库容(万m³)">{{ form.totCap }}</el-form-item></el-col>
+        <el-row :gutter="16">
+          <el-col :span="8"><el-form-item label="死水位"><el-input-number v-model="addEditForm.deadLev" :precision="2" :controls="false" style="width:100%" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="总库容"><el-input-number v-model="addEditForm.totCap" :precision="2" :controls="false" style="width:100%" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="调洪库容"><el-input-number v-model="addEditForm.storFlCap" :precision="2" :controls="false" style="width:100%" /></el-form-item></el-col>
         </el-row>
-        <el-row>
-          <el-col :span="12"><el-form-item label="正常库容(万m³)">{{ form.normPoolStagCap }}</el-form-item></el-col>
-          <el-col :span="12"><el-form-item label="防洪库容(万m³)">{{ form.flcoCap }}</el-form-item></el-col>
+        <el-row :gutter="16">
+          <el-col :span="8"><el-form-item label="防洪库容"><el-input-number v-model="addEditForm.flcoCap" :precision="2" :controls="false" style="width:100%" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="兴利库容"><el-input-number v-model="addEditForm.benResCap" :precision="2" :controls="false" style="width:100%" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="死库容"><el-input-number v-model="addEditForm.deadCap" :precision="2" :controls="false" style="width:100%" /></el-form-item></el-col>
         </el-row>
-        <el-row>
-          <el-col :span="12"><el-form-item label="调洪库容(万m³)">{{ form.storFlCap }}</el-form-item></el-col>
-          <el-col :span="12"><el-form-item label="集水面积(km²)">{{ form.watShedArea }}</el-form-item></el-col>
+        <el-row :gutter="16">
+          <el-col :span="8"><el-form-item label="工程建设情况"><el-select v-model="addEditForm.engStat" placeholder="请选择" style="width:100%"><el-option v-for="(v,k) in engStatMap" :key="k" :label="v" :value="k" /></el-select></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="开工时间"><el-date-picker v-model="addEditForm.startDate" type="date" value-format="YYYY-MM-DD" placeholder="请选择" style="width:100%" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="建成时间"><el-date-picker v-model="addEditForm.compDate" type="date" value-format="YYYY-MM-DD" placeholder="请选择" style="width:100%" /></el-form-item></el-col>
         </el-row>
-        <el-row>
-          <el-col :span="12"><el-form-item label="运行状况">{{ runStatMap[form.runStat] }}</el-form-item></el-col>
-          <el-col :span="12"><el-form-item label="管理单位">{{ form.admDep }}</el-form-item></el-col>
+        <el-row :gutter="16">
+          <el-col :span="8"><el-form-item label="归口管理部门"><el-input v-model="addEditForm.admDep" maxlength="50" /></el-form-item></el-col>
+          <el-col :span="16"><el-form-item label="备注"><el-input v-model="addEditForm.note" maxlength="200" type="textarea" :rows="2" /></el-form-item></el-col>
         </el-row>
       </el-form>
+      <template #footer>
+        <el-button @click="addEditOpen = false">取 消</el-button>
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+      </template>
     </el-dialog>
 
     <!-- 统计弹窗 -->
     <el-dialog title="统计类别" v-model="statsVisible" width="500px" append-to-body>
       <el-table :data="statsData" stripe v-loading="statsLoading">
-        <el-table-column prop="name" label="类别" min-width="120" />
-        <el-table-column prop="value" label="数量" width="120" />
+        <el-table-column prop="name" label="类别" min-width="120"><template #default="s">{{ s.row.NAME || s.row.name }}</template></el-table-column>
+        <el-table-column prop="value" label="数量" width="120"><template #default="s">{{ s.row.VALUE ?? s.row.value }}</template></el-table-column>
       </el-table>
     </el-dialog>
+
+    <!-- 导入弹窗 -->
+    <excel-import-dialog ref="importRef" title="水库信息导入" action="/slgc/reservoir/importData" template-action="/slgc/reservoir/importTemplate" template-file-name="reservoir_template" @success="getList" />
   </div>
 </template>
 
 <script setup name="Reservoir">
 import { getReservoirList } from '@/api/slgc/gc/index'
-import { getReservoir, getReservoirCountType } from '@/api/slgc/reservoir/index'
+import { getReservoir, getReservoirCountType, addReservoir, updateReservoir, delReservoir, exportReservoir } from '@/api/slgc/reservoir/index'
+import ExcelImportDialog from '@/components/ExcelImportDialog/index.vue'
 
 const { proxy } = getCurrentInstance()
 const loading = ref(true)
 const total = ref(0)
 const open = ref(false)
+const addEditOpen = ref(false)
 const list = ref([])
+const ids = ref([])
+const single = ref(true)
+const multiple = ref(true)
 const queryParams = reactive({ pageNum: 1, pageSize: 10, resName: undefined, resLoc: undefined, resType: undefined })
 const form = ref({})
+const addEditForm = ref({})
+const addEditFormRef = ref(null)
+const importRef = ref(null)
+const isEdit = ref(false)
+const dialogTitle = ref('')
 const statsVisible = ref(false)
 const statsLoading = ref(false)
 const statsData = ref([])
 const resTypeMap = { '1': '山丘水库', '2': '平原水库', '3': '地下水库' }
 const engGradMap = { '1': 'Ⅰ等', '2': 'Ⅱ等', '3': 'Ⅲ等', '4': 'Ⅳ等', '5': 'Ⅴ等' }
-const runStatMap = { '1': '在用良好', '2': '在用故障', '3': '停用' }
+const engStatMap = { '1': '已建', '2': '在建', '3': '规划' }
+const addEditRules = {
+  resName: [{ required: true, message: '请输入水库名称', trigger: 'blur' }],
+  resCode: [{ required: true, message: '请输入水库代码', trigger: 'blur' }]
+}
 
 function getList() {
   loading.value = true
@@ -103,9 +194,58 @@ function getList() {
   })
 }
 function handleQuery() { queryParams.pageNum = 1; getList() }
+function handleSelectionChange(selection) {
+  ids.value = selection.map(item => item.resCode)
+  single.value = selection.length !== 1
+  multiple.value = !selection.length
+}
 function handleDetail(row) {
   getReservoir(row.resCode).then(res => { form.value = res.data; open.value = true })
 }
+function handleAdd() {
+  isEdit.value = false
+  dialogTitle.value = '新增水库信息'
+  addEditForm.value = {}
+  addEditOpen.value = true
+}
+function handleUpdate(row) {
+  isEdit.value = true
+  dialogTitle.value = '修改水库信息'
+  const resCode = row.resCode || ids.value[0]
+  getReservoir(resCode).then(res => { addEditForm.value = res.data; addEditOpen.value = true })
+}
+function submitForm() {
+  addEditFormRef.value.validate(valid => {
+    if (!valid) return
+    if (isEdit.value) {
+      updateReservoir(addEditForm.value).then(() => { proxy.$modal.msgSuccess('修改成功'); addEditOpen.value = false; getList() })
+    } else {
+      addReservoir(addEditForm.value).then(() => { proxy.$modal.msgSuccess('新增成功'); addEditOpen.value = false; getList() })
+    }
+  })
+}
+function handleDelete(row) {
+  proxy.$modal.confirm('是否确认删除该条水库数据?').then(() => {
+    return delReservoir(row.resCode)
+  }).then(() => { getList(); proxy.$modal.msgSuccess('删除成功') }).catch(() => {})
+}
+function handleBatchDelete() {
+  if (!ids.value.length) return
+  proxy.$modal.confirm('是否确认删除选中的 ' + ids.value.length + ' 条水库数据?').then(() => {
+    return delReservoir(ids.value.join(','))
+  }).then(() => { getList(); proxy.$modal.msgSuccess('删除成功') }).catch(() => {})
+}
+function handleExport() {
+  exportReservoir(queryParams).then(res => {
+    const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
+    const url = window.URL.createObjectURL(blob)
+    const link = document.createElement('a')
+    link.href = url
+    link.download = '水库信息数据.xlsx'
+    link.click()
+    window.URL.revokeObjectURL(url)
+  })
+}
 function handleStats() {
   statsVisible.value = true
   statsLoading.value = true
@@ -131,6 +271,13 @@ onMounted(() => { getList() })
   align-items: center;
   justify-content: space-between;
   margin-bottom: 16px;
+  flex-wrap: wrap;
+  gap: 8px;
+}
+.gc-toolbar-left {
+  display: flex;
+  align-items: center;
+  gap: 8px;
 }
 .gc-toolbar-right {
   display: flex;
@@ -143,4 +290,17 @@ onMounted(() => { getList() })
   display: flex;
   justify-content: flex-end;
 }
+/* 详情弹窗:el-descriptions 标签加粗、居中 */
+:deep(.gc-detail-dialog .el-descriptions__label) {
+  font-weight: 500;
+  color: #303133;
+  background-color: #f5f7fa;
+}
+:deep(.gc-detail-dialog .el-descriptions__content) {
+  color: #606266;
+}
+/* 编辑弹窗:更紧凑 */
+.gc-edit-form :deep(.el-form-item) {
+  margin-bottom: 18px;
+}
 </style>

+ 1 - 0
gw-ui/src/views/slgc/sw/index.vue

@@ -37,6 +37,7 @@
   </div>
 </template>
 <script setup name="Sw">
+import { ElMessage, ElMessageBox } from 'element-plus'
 import { getSwInfoList, getSwJmList, getSwSjList, getSwTpList } from '@/api/slgc/sw/index'
 const activeTab = ref('info')
 const loading = ref(false); const stnm = ref('')

+ 63 - 16
gw-ui/src/views/slgc/unit/file.vue

@@ -10,22 +10,34 @@
           <el-button type="primary" size="small" @click="uploadDialogVisible = true">上传文件</el-button>
         </div>
       </template>
-      <el-dialog v-model="uploadDialogVisible" title="上传文件" width="480px" :close-on-click-modal="false" @closed="resetUploadForm">
+      <el-dialog v-model="uploadDialogVisible" title="上传文件" width="400px" append-to-body :close-on-click-modal="false" @closed="resetUploadForm">
         <el-form label-width="80px">
           <el-form-item label="标题">
             <el-input v-model="fileTitle" placeholder="请输入文件标题" maxlength="100" />
           </el-form-item>
+          <el-form-item label="选择文件">
+            <el-upload
+              ref="uploadRef"
+              :action="uploadUrl"
+              :data="{ fileCate: 'UNIT-' + unitId, fileTitle: fileTitle }"
+              :headers="uploadHeaders"
+              :limit="1"
+              :on-success="handleUploadSuccess"
+              :on-error="handleUploadError"
+              :on-change="handleUploadFileChange"
+              :on-remove="handleUploadFileRemove"
+              :before-upload="beforeUpload"
+              :auto-upload="false"
+              :show-file-list="true">
+              <el-button type="primary" size="small" icon="Upload">选择文件</el-button>
+              <template #tip><div class="el-upload__tip">支持PDF、Word等常见文件</div></template>
+            </el-upload>
+          </el-form-item>
         </el-form>
-        <el-upload
-          ref="uploadRef"
-          :action="uploadUrl"
-          :data="{ fileCate: 'UNIT-' + unitId, fileTitle: fileTitle }"
-          :show-file-list="false"
-          :before-upload="beforeUpload"
-          :on-success="handleUploadSuccess"
-          :on-error="handleUploadError">
-          <el-button type="primary" size="small">选择文件</el-button>
-        </el-upload>
+        <template #footer>
+          <el-button type="primary" :loading="isUploading" @click="submitUpload">{{ isUploading ? '上传中...' : '上 传' }}</el-button>
+          <el-button @click="uploadDialogVisible = false">取 消</el-button>
+        </template>
       </el-dialog>
       <div v-if="fileList.length === 0">
         <el-empty description="暂无档案文件" />
@@ -33,7 +45,7 @@
       <div v-else class="file-list">
         <div v-for="file in fileList" :key="file.fileId" class="file-item" @mouseover="hoverId = file.fileId" @mouseout="hoverId = ''">
           <el-icon size="16" color="#409EFF"><Document /></el-icon>
-          <a :href="getDownloadUrl(file)" target="_blank" class="file-name">{{ file.fileName }}</a>
+          <a href="javascript:void(0)" class="file-name" @click="handleDownloadFile(file)">{{ file.fileName }}</a>
           <span class="file-size">{{ formatSize(file.fileSize) }}</span>
           <span class="file-time">{{ file.createTime }}</span>
           <el-button v-show="hoverId === file.fileId" type="danger" link size="small" @click.stop="handleDeleteFile(file)">删除</el-button>
@@ -44,7 +56,10 @@
 </template>
 
 <script setup name="UnitFile">
+import { ElMessage, ElMessageBox } from 'element-plus'
 import { getDocFileList, delDocFile } from '@/api/slgc/doc/index'
+import { getToken } from '@/utils/auth'
+import request from '@/utils/request'
 
 const route = useRoute()
 const { proxy } = getCurrentInstance()
@@ -52,7 +67,9 @@ const uploadRef = ref(null)
 const unitId = ref(route.query.id || '')
 const fileList = ref([])
 const hoverId = ref('')
-const uploadUrl = ref('/slgc/doc-file/upload')
+const uploadUrl = ref(import.meta.env.VITE_SERVICE_BASE_TITLE + '/slgc/doc-file/upload')
+const uploadHeaders = { Authorization: 'Bearer ' + getToken() }
+const isUploading = ref(false)
 const uploadDialogVisible = ref(false)
 const fileTitle = ref('')
 
@@ -63,8 +80,17 @@ function loadFiles() {
   })
 }
 
-function getDownloadUrl(file) {
-  return `/common/download/resource?resource=${encodeURIComponent(file.filePath)}`
+function handleDownloadFile(file) {
+  if (!file.filePath) return
+  request({ url: '/common/download/resource', method: 'get', params: { resource: file.filePath }, responseType: 'blob' }).then(res => {
+    const blob = new Blob([res])
+    const url = window.URL.createObjectURL(blob)
+    const link = document.createElement('a')
+    link.href = url
+    link.download = file.fileName || '下载文件'
+    link.click()
+    window.URL.revokeObjectURL(url)
+  }).catch(() => { ElMessage.error('下载失败') })
 }
 
 function formatSize(size) {
@@ -85,9 +111,10 @@ function beforeUpload(file) {
 }
 
 function handleUploadSuccess(res) {
+  isUploading.value = false
+  uploadDialogVisible.value = false
   if (res.code === 200) {
     ElMessage.success('上传成功')
-    uploadDialogVisible.value = false
     fileTitle.value = ''
     loadFiles()
   } else {
@@ -95,13 +122,33 @@ function handleUploadSuccess(res) {
   }
 }
 
+function handleUploadFileChange(file) {
+  if (!fileTitle.value) fileTitle.value = file.name || file.raw?.name || ''
+}
+
+function handleUploadFileRemove() {
+  fileTitle.value = ''
+}
+
 function resetUploadForm() {
   fileTitle.value = ''
+  isUploading.value = false
+  uploadRef.value?.clearFiles()
 }
 
 function handleUploadError() {
+  isUploading.value = false
   ElMessage.error('上传失败')
 }
+function submitUpload() {
+  if (!uploadRef.value) return
+  isUploading.value = true
+  try {
+    uploadRef.value.submit()
+  } catch (e) {
+    isUploading.value = false
+  }
+}
 
 function handleDeleteFile(file) {
   proxy.$modal.confirm('确认删除"' + file.fileName + '"?').then(() => {

+ 55 - 12
gw-ui/src/views/slgc/unit/index.vue

@@ -151,14 +151,14 @@
           <el-form-item label="文件标题"><el-input v-model="uploadTitle" placeholder="请输入" /></el-form-item>
           <el-form-item label="选择文件">
             <el-upload ref="uploadRef" :action="uploadAction" :data="uploadData" :headers="uploadHeaders"
-              :limit="1" :on-success="handleUploadSuccess" :before-upload="beforeUpload" :auto-upload="false">
+              :limit="1" :on-success="handleUploadSuccess" :on-error="handleUploadError" :on-change="handleUploadFileChange" :on-remove="handleUploadFileRemove" :before-upload="beforeUpload" :auto-upload="false" :show-file-list="true">
               <el-button type="primary" size="small" icon="Upload">选择文件</el-button>
               <template #tip><div class="el-upload__tip">支持PDF、Word等常见文件</div></template>
             </el-upload>
           </el-form-item>
         </el-form>
         <template #footer>
-          <el-button type="primary" @click="$refs.uploadRef.submit()">上 传</el-button>
+          <el-button type="primary" :loading="isUploading" @click="submitUpload">{{ isUploading ? '上传中...' : '上 传' }}</el-button>
           <el-button @click="uploadVisible = false">取 消</el-button>
         </template>
       </el-dialog>
@@ -194,6 +194,7 @@
 
 <script setup name="Unit">
 import { getToken } from '@/utils/auth'
+import request from '@/utils/request'
 import { getUnitList, getUnit, saveUnit, delUnit, getRecordList, saveRecord, delRecord, exportUnit, importTemplateUrl } from '@/api/slgc/unit/index'
 import { getDocFileList, delDocFile } from '@/api/slgc/doc/index'
 
@@ -223,11 +224,12 @@ const uploadTitle = ref('')
 const uploadAction = ref('')
 const uploadData = ref({})
 const uploadHeaders = ref({ Authorization: 'Bearer ' + getToken() })
+const isUploading = ref(false)
 
 // === 导入导出 ===
 const importVisible = ref(false)
 const importUploadRef = ref(null)
-const importUrl = ref('/slgc/water-unit/importData')
+const importUrl = ref(import.meta.env.VITE_SERVICE_BASE_TITLE + '/slgc/water-unit/importData')
 
 function formatFileSize(bytes) {
   if (!bytes) return '-'
@@ -295,7 +297,15 @@ function handleExport() {
 }
 
 function handleDownloadTemplate() {
-  window.open(importTemplateUrl(), '_blank')
+  request({ url: importTemplateUrl(), method: 'get', responseType: 'blob' }).then(res => {
+    const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
+    const url = window.URL.createObjectURL(blob)
+    const link = document.createElement('a')
+    link.href = url
+    link.download = '用水单位导入模板.xlsx'
+    link.click()
+    window.URL.revokeObjectURL(url)
+  })
 }
 
 function handleImportSuccess(res) {
@@ -378,31 +388,64 @@ function loadFiles(unitId) {
 function handleUploadFile() {
   uploadTitle.value = ''
   // 相对路径,走 vite 代理;fileCate 前缀 UNIT- 关联单位
-  uploadAction.value = '/slgc/doc-file/upload'
-  uploadData.value = { fileCate: 'UNIT-' + fileUnitId.value, fileTitle: '' }
+  uploadAction.value = import.meta.env.VITE_SERVICE_BASE_TITLE + '/slgc/doc-file/upload'
+  uploadData.value = { fileCate: 'UNIT-' + fileUnitId.value, fileDesc: '' }
   uploadVisible.value = true
 }
 
 function beforeUpload(file) {
-  uploadData.value.fileTitle = uploadTitle.value || file.name
+  uploadData.value.fileDesc = uploadTitle.value || file.name
   return true
 }
 
+function submitUpload() {
+  if (!uploadRef.value) return
+  isUploading.value = true
+  try {
+    uploadRef.value.submit()
+  } catch (e) {
+    isUploading.value = false
+  }
+}
+
+function handleUploadFileChange(file) {
+  // el-upload选择文件时同步设置文件标题
+  if (!uploadTitle.value) {
+    uploadTitle.value = file.name || file.raw?.name || ''
+  }
+}
+
+function handleUploadFileRemove() {
+  uploadTitle.value = ''
+}
+
 function handleUploadSuccess(res) {
+  isUploading.value = false
+  uploadVisible.value = false
   if (res.code === 200) {
     proxy.$modal.msgSuccess('上传成功')
-    uploadVisible.value = false
     loadFiles()
   } else {
     proxy.$modal.msgError(res.msg || '上传失败')
   }
 }
 
+function handleUploadError() {
+  isUploading.value = false
+  proxy.$modal.msgError('上传失败,请检查网络或文件大小')
+}
+
 function handleDownloadFile(row) {
-  if (row.filePath) {
-    // 与上传目录一致(profile/upload),走资源下载接口
-    window.open(`/common/download/resource?resource=${encodeURIComponent(row.filePath)}`, '_blank')
-  }
+  if (!row.filePath) return
+  request({ url: '/common/download/resource', method: 'get', params: { resource: row.filePath }, responseType: 'blob' }).then(res => {
+    const blob = new Blob([res])
+    const url = window.URL.createObjectURL(blob)
+    const link = document.createElement('a')
+    link.href = url
+    link.download = row.fileName || '下载文件'
+    link.click()
+    window.URL.revokeObjectURL(url)
+  })
 }
 
 function handleDeleteFile(row) {

+ 139 - 81
gw-ui/src/views/slgc/waga/index.vue

@@ -1,32 +1,36 @@
-<template>
+<template>
   <div class="gc-page">
     <div class="gc-toolbar">
-      <el-button type="success" icon="DataAnalysis" @click="handleStats">统计</el-button>
+      <div class="gc-toolbar-left">
+        <el-button type="success" icon="DataAnalysis" @click="handleStats">统计</el-button>
+        <el-button type="primary" icon="Plus" @click="handleAdd">新增</el-button>
+        <el-button type="warning" icon="Edit" :disabled="single" @click="handleUpdate">修改</el-button>
+        <el-button type="danger" icon="Delete" :disabled="multiple" @click="handleBatchDelete">删除</el-button>
+        <el-button type="info" icon="Upload" @click="importRef.open()">导入</el-button>
+        <el-button type="success" icon="Download" @click="handleExport">导出</el-button>
+      </div>
       <div class="gc-toolbar-right">
         <el-input v-model="queryParams.wagaName" placeholder="请输入名称" clearable style="width: 160px" @keyup.enter="handleQuery" />
         <el-input v-model="queryParams.wagaLoc" placeholder="请输入位置" clearable style="width: 160px" @keyup.enter="handleQuery" />
         <el-select v-model="queryParams.wagaType" placeholder="请选择" clearable style="width: 140px">
-          <el-option label="分(泄)洪闸" value="1" />
-          <el-option label="节制闸" value="2" />
-          <el-option label="排(退)水闸" value="3" />
-          <el-option label="引(进)水闸" value="4" />
-          <el-option label="挡潮闸" value="5" />
-          <el-option label="船闸" value="6" />
-          <el-option label="其他" value="9" />
+          <el-option label="分(泄)洪闸" value="1" /><el-option label="节制闸" value="2" /><el-option label="排(退)水闸" value="3" /><el-option label="引(进)水闸" value="4" /><el-option label="挡潮闸" value="5" /><el-option label="船闸" value="6" /><el-option label="其他" value="9" />
         </el-select>
         <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
       </div>
     </div>
 
-    <el-table v-loading="loading" :data="list" stripe border>
+    <el-table v-loading="loading" :data="list" stripe border style="width: 100%" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
       <el-table-column prop="wagaName" label="水闸名称" min-width="180" />
       <el-table-column prop="wagaLoc" label="水闸所在位置" min-width="220" />
       <el-table-column prop="engGrad" label="工程等别" width="120">
         <template #default="s">{{ engGradMap[s.row.engGrad] || s.row.engGrad }}</template>
       </el-table-column>
-      <el-table-column label="操作" width="100" fixed="right" align="center">
+      <el-table-column label="操作" width="180" fixed="right" align="center">
         <template #default="s">
           <el-button type="primary" link icon="View" @click="handleDetail(s.row)">查看</el-button>
+          <el-button type="warning" link icon="Edit" @click="handleUpdate(s.row)">修改</el-button>
+          <el-button type="danger" link icon="Delete" @click="handleDelete(s.row)">删除</el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -36,117 +40,171 @@
     </div>
 
     <!-- 查看详情弹窗 -->
-    <el-dialog :title="form.wagaName" v-model="open" width="720px" append-to-body>
-      <el-form :model="form" label-width="130px" :disabled="true">
-        <el-row>
-          <el-col :span="12"><el-form-item label="水闸名称">{{ form.wagaName }}</el-form-item></el-col>
-          <el-col :span="12"><el-form-item label="水闸代码">{{ form.wagaCode }}</el-form-item></el-col>
-        </el-row>
-        <el-row>
-          <el-col :span="12"><el-form-item label="起点经度">{{ form.startLon }}</el-form-item></el-col>
-          <el-col :span="12"><el-form-item label="起点纬度">{{ form.startLat }}</el-form-item></el-col>
-        </el-row>
-        <el-row>
-          <el-col :span="12"><el-form-item label="终点经度">{{ form.endLon }}</el-form-item></el-col>
-          <el-col :span="12"><el-form-item label="终点纬度">{{ form.endLat }}</el-form-item></el-col>
-        </el-row>
-        <el-row>
-          <el-col :span="12"><el-form-item label="水闸所在位置">{{ form.wagaLoc }}</el-form-item></el-col>
-          <el-col :span="12"><el-form-item label="水闸类型">{{ wagaTypeMap[form.wagaType] }}</el-form-item></el-col>
+    <el-dialog :title="form.wagaName" v-model="open" width="900px" append-to-body class="gc-detail-dialog">
+      <el-descriptions :column="3" border>
+        <el-descriptions-item label="水闸名称">{{ form.wagaName }}</el-descriptions-item>
+        <el-descriptions-item label="水闸代码">{{ form.wagaCode }}</el-descriptions-item>
+        <el-descriptions-item label="起点经度">{{ form.startLong }}</el-descriptions-item>
+        <el-descriptions-item label="起点纬度">{{ form.startLat }}</el-descriptions-item>
+        <el-descriptions-item label="终点经度">{{ form.endLong }}</el-descriptions-item>
+        <el-descriptions-item label="终点纬度">{{ form.endLat }}</el-descriptions-item>
+        <el-descriptions-item label="水闸所在位置" :span="2">{{ form.wagaLoc }}</el-descriptions-item>
+        <el-descriptions-item label="水闸类型">{{ wagaTypeMap[form.wagaType] }}</el-descriptions-item>
+        <el-descriptions-item label="水闸用途">{{ form.wagaUse }}</el-descriptions-item>
+        <el-descriptions-item label="工程等别">{{ engGradMap[form.engGrad] }}</el-descriptions-item>
+        <el-descriptions-item label="工程规模">{{ form.engScal }}</el-descriptions-item>
+        <el-descriptions-item label="最大过闸流量">{{ form.lockDisc }}</el-descriptions-item>
+        <el-descriptions-item label="闸孔数量">{{ form.gaorNum }}</el-descriptions-item>
+        <el-descriptions-item label="工程建设情况">{{ engStatMap[form.engStat] }}</el-descriptions-item>
+        <el-descriptions-item label="开工时间">{{ form.startDate }}</el-descriptions-item>
+        <el-descriptions-item label="建成时间">{{ form.compDate }}</el-descriptions-item>
+        <el-descriptions-item label="归口管理部门">{{ form.admDep }}</el-descriptions-item>
+        <el-descriptions-item label="备注" :span="3">{{ form.note }}</el-descriptions-item>
+      </el-descriptions>
+    </el-dialog>
+
+    <!-- 新增/编辑弹窗 -->
+    <el-dialog :title="dialogTitle" v-model="addEditOpen" width="900px" append-to-body>
+      <el-form ref="addEditFormRef" :model="addEditForm" :rules="addEditRules" label-width="140px" class="gc-edit-form">
+        <el-row :gutter="16">
+          <el-col :span="8"><el-form-item label="水闸名称" prop="wagaName"><el-input v-model="addEditForm.wagaName" maxlength="50" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="水闸代码" prop="wagaCode"><el-input v-model="addEditForm.wagaCode" maxlength="50" :disabled="isEdit" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="起点经度"><el-input-number v-model="addEditForm.startLong" :precision="4" :controls="false" style="width:100%" /></el-form-item></el-col>
         </el-row>
-        <el-row>
-          <el-col :span="12"><el-form-item label="水闸用途">{{ form.wagaUsage }}</el-form-item></el-col>
-          <el-col :span="12"><el-form-item label="工程等别">{{ engGradMap[form.engGrad] }}</el-form-item></el-col>
+        <el-row :gutter="16">
+          <el-col :span="8"><el-form-item label="起点纬度"><el-input-number v-model="addEditForm.startLat" :precision="4" :controls="false" style="width:100%" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="终点经度"><el-input-number v-model="addEditForm.endLong" :precision="4" :controls="false" style="width:100%" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="终点纬度"><el-input-number v-model="addEditForm.endLat" :precision="4" :controls="false" style="width:100%" /></el-form-item></el-col>
         </el-row>
-        <el-row>
-          <el-col :span="12"><el-form-item label="工程规模">{{ form.engScale }}</el-form-item></el-col>
-          <el-col :span="12"><el-form-item label="最大过闸流量">{{ form.lockDisc }}</el-form-item></el-col>
+        <el-row :gutter="16">
+          <el-col :span="8"><el-form-item label="水闸所在位置"><el-input v-model="addEditForm.wagaLoc" maxlength="50" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="水闸类型"><el-select v-model="addEditForm.wagaType" placeholder="请选择" style="width:100%"><el-option v-for="(v,k) in wagaTypeMap" :key="k" :label="v" :value="k" /></el-select></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="水闸用途"><el-input v-model="addEditForm.wagaUse" maxlength="50" /></el-form-item></el-col>
         </el-row>
-        <el-row>
-          <el-col :span="12"><el-form-item label="闸孔数量">{{ form.lockNum }}</el-form-item></el-col>
-          <el-col :span="12"><el-form-item label="工程建设情况">{{ form.engBuild }}</el-form-item></el-col>
+        <el-row :gutter="16">
+          <el-col :span="8"><el-form-item label="工程等别"><el-select v-model="addEditForm.engGrad" placeholder="请选择" style="width:100%"><el-option v-for="(v,k) in engGradMap" :key="k" :label="v" :value="k" /></el-select></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="工程规模"><el-select v-model="addEditForm.engScal" placeholder="请选择" style="width:100%"><el-option label="大(1)型" value="1" /><el-option label="大(2)型" value="2" /><el-option label="中型" value="3" /><el-option label="小(1)型" value="4" /><el-option label="小(2)型" value="5" /></el-select></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="最大过闸流量"><el-input-number v-model="addEditForm.lockDisc" :precision="2" :controls="false" style="width:100%" /></el-form-item></el-col>
         </el-row>
-        <el-row>
-          <el-col :span="12"><el-form-item label="开工时间">{{ form.startDate }}</el-form-item></el-col>
-          <el-col :span="12"><el-form-item label="建成时间">{{ form.endDate }}</el-form-item></el-col>
+        <el-row :gutter="16">
+          <el-col :span="8"><el-form-item label="闸孔数量"><el-input-number v-model="addEditForm.gaorNum" :controls="false" style="width:100%" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="工程建设情况"><el-select v-model="addEditForm.engStat" placeholder="请选择" style="width:100%"><el-option v-for="(v,k) in engStatMap" :key="k" :label="v" :value="k" /></el-select></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="开工时间"><el-date-picker v-model="addEditForm.startDate" type="date" value-format="YYYY-MM-DD" placeholder="请选择" style="width:100%" /></el-form-item></el-col>
         </el-row>
-        <el-row>
-          <el-col :span="12"><el-form-item label="归口管理部门">{{ form.admDep }}</el-form-item></el-col>
+        <el-row :gutter="16">
+          <el-col :span="8"><el-form-item label="建成时间"><el-date-picker v-model="addEditForm.compDate" type="date" value-format="YYYY-MM-DD" placeholder="请选择" style="width:100%" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="归口管理部门"><el-input v-model="addEditForm.admDep" maxlength="50" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="备注"><el-input v-model="addEditForm.note" maxlength="50" /></el-form-item></el-col>
         </el-row>
       </el-form>
+      <template #footer>
+        <el-button @click="addEditOpen = false">取 消</el-button>
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+      </template>
     </el-dialog>
 
-    <!-- 统计弹窗 -->
     <el-dialog title="统计类别" v-model="statsVisible" width="500px" append-to-body>
       <el-table :data="statsData" stripe v-loading="statsLoading">
-        <el-table-column prop="name" label="类别" min-width="120" />
-        <el-table-column prop="value" label="数量" width="120" />
+        <el-table-column prop="name" label="类别" min-width="120"><template #default="s">{{ s.row.NAME || s.row.name }}</template></el-table-column>
+        <el-table-column prop="value" label="数量" width="120"><template #default="s">{{ s.row.VALUE ?? s.row.value }}</template></el-table-column>
       </el-table>
     </el-dialog>
+
+    <excel-import-dialog ref="importRef" title="水闸信息导入" action="/slgc/waga/importData" template-action="/slgc/waga/importTemplate" template-file-name="waga_template" @success="getList" />
   </div>
 </template>
 
 <script setup name="Waga">
 import { getWagaList } from '@/api/slgc/gc/index'
-import { getWaga, getWagaCountType } from '@/api/slgc/waga/index'
+import { getWaga, getWagaCountType, addWaga, updateWaga, delWaga, exportWaga } from '@/api/slgc/waga/index'
+import ExcelImportDialog from '@/components/ExcelImportDialog/index.vue'
 
+const { proxy } = getCurrentInstance()
 const loading = ref(true)
 const total = ref(0)
 const open = ref(false)
+const addEditOpen = ref(false)
 const list = ref([])
+const ids = ref([])
+const single = ref(true)
+const multiple = ref(true)
 const queryParams = reactive({ pageNum: 1, pageSize: 10, wagaName: undefined, wagaLoc: undefined, wagaType: undefined })
 const form = ref({})
+const addEditForm = ref({})
+const addEditFormRef = ref(null)
+const importRef = ref(null)
+const isEdit = ref(false)
+const dialogTitle = ref('')
 const statsVisible = ref(false)
 const statsLoading = ref(false)
 const statsData = ref([])
 const wagaTypeMap = { '1': '分(泄)洪闸', '2': '节制闸', '3': '排(退)水闸', '4': '引(进)水闸', '5': '挡潮闸', '6': '船闸', '9': '其他' }
 const engGradMap = { '1': 'Ⅰ等', '2': 'Ⅱ等', '3': 'Ⅲ等', '4': 'Ⅳ等', '5': 'Ⅴ等' }
+const engStatMap = { '1': '已建', '2': '在建', '3': '规划' }
+const addEditRules = {
+  wagaName: [{ required: true, message: '请输入水闸名称', trigger: 'blur' }],
+  wagaCode: [{ required: true, message: '请输入水闸代码', trigger: 'blur' }]
+}
 
 function getList() {
   loading.value = true
-  getWagaList(queryParams).then(res => {
-    list.value = res.rows; total.value = res.total; loading.value = false
-  })
+  getWagaList(queryParams).then(res => { list.value = res.rows; total.value = res.total; loading.value = false })
 }
 function handleQuery() { queryParams.pageNum = 1; getList() }
-function handleDetail(row) {
-  getWaga(row.wagaCode).then(res => { form.value = res.data; open.value = true })
+function handleSelectionChange(selection) {
+  ids.value = selection.map(item => item.wagaCode)
+  single.value = selection.length !== 1
+  multiple.value = !selection.length
+}
+function handleDetail(row) { getWaga(row.wagaCode).then(res => { form.value = res.data; open.value = true }) }
+function handleAdd() { isEdit.value = false; dialogTitle.value = '新增水闸信息'; addEditForm.value = {}; addEditOpen.value = true }
+function handleUpdate(row) {
+  isEdit.value = true; dialogTitle.value = '修改水闸信息'
+  getWaga(row.wagaCode || ids.value[0]).then(res => { addEditForm.value = res.data; addEditOpen.value = true })
+}
+function submitForm() {
+  addEditFormRef.value.validate(valid => {
+    if (!valid) return
+    const fn = isEdit.value ? updateWaga : addWaga
+    fn(addEditForm.value).then(() => { proxy.$modal.msgSuccess(isEdit.value ? '修改成功' : '新增成功'); addEditOpen.value = false; getList() })
+  })
+}
+function handleDelete(row) {
+  proxy.$modal.confirm('是否确认删除该条水闸数据?').then(() => {
+    return delWaga(row.wagaCode)
+  }).then(() => { getList(); proxy.$modal.msgSuccess('删除成功') }).catch(() => {})
+}
+function handleBatchDelete() {
+  if (!ids.value.length) return
+  proxy.$modal.confirm('是否确认删除选中的 ' + ids.value.length + ' 条水闸数据?').then(() => {
+    return delWaga(ids.value.join(','))
+  }).then(() => { getList(); proxy.$modal.msgSuccess('删除成功') }).catch(() => {})
+}
+function handleExport() {
+  exportWaga(queryParams).then(res => {
+    const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
+    const url = window.URL.createObjectURL(blob)
+    const link = document.createElement('a')
+    link.href = url
+    link.download = '水闸信息数据.xlsx'
+    link.click()
+    window.URL.revokeObjectURL(url)
+  })
 }
 function handleStats() {
-  statsVisible.value = true
-  statsLoading.value = true
-  getWagaCountType().then(res => {
-    statsData.value = res.data || []
-    statsLoading.value = false
-  }).catch(() => { statsLoading.value = false })
+  statsVisible.value = true; statsLoading.value = true
+  getWagaCountType().then(res => { statsData.value = res.data || []; statsLoading.value = false }).catch(() => { statsLoading.value = false })
 }
 
 onMounted(() => { getList() })
 </script>
 
 <style scoped>
-.gc-page {
-  padding: 16px;
-  background: #fff;
-  min-height: calc(100vh - 60px);
-  display: flex;
-  flex-direction: column;
-}
-.gc-toolbar {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  margin-bottom: 16px;
-}
-.gc-toolbar-right {
-  display: flex;
-  align-items: center;
-  gap: 8px;
-}
-.gc-pagination {
-  margin-top: auto;
-  padding-top: 16px;
-  display: flex;
-  justify-content: flex-end;
-}
+.gc-page { padding: 16px; background: #fff; min-height: calc(100vh - 60px); display: flex; flex-direction: column; }
+.gc-toolbar { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; flex-wrap: wrap; gap: 8px; }
+.gc-toolbar-left, .gc-toolbar-right { display: flex; align-items: center; gap: 8px; }
+.gc-pagination { margin-top: auto; padding-top: 16px; display: flex; justify-content: flex-end; }
+:deep(.gc-detail-dialog .el-descriptions__label) { font-weight: 500; color: #303133; background-color: #f5f7fa; }
+:deep(.gc-detail-dialog .el-descriptions__content) { color: #606266; }
+.gc-edit-form :deep(.el-form-item) { margin-bottom: 18px; }
 </style>

+ 1 - 0
gw-ui/src/views/slgc/waterFunction/index.vue

@@ -27,6 +27,7 @@
   </div>
 </template>
 <script setup name="WaterFunction">
+import { ElMessage, ElMessageBox } from 'element-plus'
 import { getWaterFuncList } from '@/api/slgc/waterfunc/index'
 const loading = ref(true); const list = ref([])
 const q = reactive({ name: '', river: '', 达标: '' })

+ 1 - 0
gw-ui/src/views/slgc/wco/km/index.vue

@@ -39,6 +39,7 @@
   </div>
 </template>
 <script setup name="WcoKm">
+import { ElMessage, ElMessageBox } from 'element-plus'
 import { getWcoKmList } from '@/api/slgc/wco/index'
 const loading = ref(true); const total = ref(0)
 const list = ref([])

+ 62 - 14
gw-ui/src/views/slgc/zggc/file/index.vue

@@ -7,25 +7,42 @@
             <el-button text @click="$router.back()">← 返回</el-button>
             <span>工程附件</span>
           </el-space>
-          <el-upload
-            ref="uploadRef"
-            :action="uploadUrl"
-            :data="{ fileCate: stcd }"
-            :show-file-list="false"
-            :before-upload="beforeUpload"
-            :on-success="handleUploadSuccess"
-            :on-error="handleUploadError">
-            <el-button type="primary" size="small">上传文件</el-button>
-          </el-upload>
+          <el-button type="primary" size="small" @click="uploadVisible = true">上传文件</el-button>
         </div>
       </template>
+      <el-dialog v-model="uploadVisible" title="上传文件" width="400px" append-to-body @closed="resetUploadForm">
+        <el-form label-width="80px">
+          <el-form-item label="选择文件">
+            <el-upload
+              ref="uploadRef"
+              :action="uploadUrl"
+              :data="{ fileCate: stcd }"
+              :headers="uploadHeaders"
+              :limit="1"
+              :on-success="handleUploadSuccess"
+              :on-error="handleUploadError"
+              :on-change="handleUploadFileChange"
+              :on-remove="handleUploadFileRemove"
+              :before-upload="beforeUpload"
+              :auto-upload="false"
+              :show-file-list="true">
+              <el-button type="primary" size="small" icon="Upload">选择文件</el-button>
+              <template #tip><div class="el-upload__tip">支持PDF、Word等常见文件,大小不超过50MB</div></template>
+            </el-upload>
+          </el-form-item>
+        </el-form>
+        <template #footer>
+          <el-button type="primary" :loading="isUploading" @click="submitUpload">{{ isUploading ? '上传中...' : '上 传' }}</el-button>
+          <el-button @click="uploadVisible = false">取 消</el-button>
+        </template>
+      </el-dialog>
       <div v-if="fileList.length === 0" class="empty-tip">
         <el-empty description="暂无附件文件" />
       </div>
       <div v-else class="file-list">
         <div v-for="file in fileList" :key="file.fileId" class="file-item" @mouseover="hoverId=file.fileId" @mouseout="hoverId=''">
           <el-icon size="16" color="#409EFF"><Document /></el-icon>
-          <a :href="getDownloadUrl(file)" target="_blank" class="file-name">{{ file.fileName }}</a>
+          <a href="javascript:void(0)" class="file-name" @click="handleDownloadFile(file)">{{ file.fileName }}</a>
           <span class="file-size">{{ formatSize(file.fileSize) }}</span>
           <el-button v-show="hoverId===file.fileId" type="danger" link size="small" @click.stop="handleDeleteFile(file)">删除</el-button>
         </div>
@@ -34,19 +51,36 @@
   </div>
 </template>
 <script setup name="StationFile">
+import { ElMessage, ElMessageBox } from 'element-plus'
 import { getDocFileList, delDocFile } from '@/api/slgc/doc/index'
+import { getToken } from '@/utils/auth'
+import request from '@/utils/request'
 const route = useRoute()
 const { proxy } = getCurrentInstance()
 const uploadRef = ref(null)
 const stcd = ref(route.query.stcd || '')
 const fileList = ref([])
 const hoverId = ref('')
-const uploadUrl = ref('/slgc/doc-file/upload')
+const uploadUrl = ref(import.meta.env.VITE_SERVICE_BASE_TITLE + '/slgc/doc-file/upload')
+const uploadVisible = ref(false)
+const uploadHeaders = { Authorization: 'Bearer ' + getToken() }
+const isUploading = ref(false)
 
 function loadFiles() {
   getDocFileList({ fileCate: stcd.value }).then(r => { fileList.value = r.rows || [] })
 }
-function getDownloadUrl(file) { return `/common/download?fileName=${encodeURIComponent(file.filePath)}` }
+function handleDownloadFile(file) {
+  if (!file.filePath) return
+  request({ url: '/common/download', method: 'get', params: { fileName: file.filePath }, responseType: 'blob' }).then(res => {
+    const blob = new Blob([res])
+    const url = window.URL.createObjectURL(blob)
+    const link = document.createElement('a')
+    link.href = url
+    link.download = file.fileName || '下载文件'
+    link.click()
+    window.URL.revokeObjectURL(url)
+  }).catch(() => { ElMessage.error('下载失败') })
+}
 function formatSize(size) {
   if (!size) return ''
   const s = Number(size)
@@ -63,6 +97,8 @@ function beforeUpload(file) {
   return true
 }
 function handleUploadSuccess(res) {
+  isUploading.value = false
+  uploadVisible.value = false
   if (res.code === 200) {
     ElMessage.success('上传成功')
     loadFiles()
@@ -70,12 +106,24 @@ function handleUploadSuccess(res) {
     ElMessage.error(res.msg || '上传失败')
   }
 }
-function handleUploadError() { ElMessage.error('上传失败') }
+function handleUploadError() { isUploading.value = false; ElMessage.error('上传失败') }
+function handleUploadFileChange(file) { }
+function handleUploadFileRemove() { }
+function resetUploadForm() { isUploading.value = false; uploadRef.value?.clearFiles() }
 function handleDeleteFile(file) {
   proxy.$modal.confirm('确认删除"' + file.fileName + '"?').then(() => {
     delDocFile(file.fileId).then(() => { ElMessage.success('删除成功'); loadFiles() })
   })
 }
+function submitUpload() {
+  if (!uploadRef.value) return
+  isUploading.value = true
+  try {
+    uploadRef.value.submit()
+  } catch (e) {
+    isUploading.value = false
+  }
+}
 onMounted(() => { loadFiles() })
 </script>
 <style scoped>

+ 1 - 0
gw-ui/src/views/slgc/zggc/portal.vue

@@ -115,6 +115,7 @@
 </template>
 
 <script setup name="ZggcPortal">
+import { ElMessage, ElMessageBox } from 'element-plus'
 import { Monitor, VideoCamera, Folder, Refresh, WarningFilled, Notification } from '@element-plus/icons-vue'
 import { getPortalData, getWeeklyData } from '@/api/slgc/zggc/index'
 import { useMap } from '@/composables/useMap'

+ 1 - 0
gw-ui/src/views/slgc/zggc/video/index.vue

@@ -167,6 +167,7 @@
   </div>
 </template>
 <script setup name="VideoMonitor">
+import { ElMessage, ElMessageBox } from 'element-plus'
 const playWndRef = ref(null)
 const layout = ref('1x1')
 const currentCamera = ref('5fe21f4e48ba4e14931e9c7794081060')