dumingliang 16 часов назад
Родитель
Сommit
080e26fe9d

+ 12 - 0
.trae/specs/dataCommon/data业务域

@@ -0,0 +1,12 @@
+| 业务域 | 数据库表 | 需要展示的字段 | 对应字段中文名称 |
+| :--- | :--- | :--- | :--- |
+| TBA_SW_1H2H | GX_RIVER_H | rvnm,stnm,spt(YYYY-MM-DD HH:mm),upz,q |河流名称,站点名称,监测时间,水位,流量 |
+| TBA_SZY_SJ_RIVER| | GX_AWQMD_BASE、GX_AWQMD_METAL、| 测站名称,时间(YYYY-MM),水温,PH值,溶解氧,高猛酸盐指数,氨氮,总磷,总氮,五日生化需氧量,化学需氧量,氰化物,氟化物,硫化物,阴离子表面活性剂,汞,铜,铅,镉,锌,砷,六价石油类、挥发酚、叶绿素a、透明度|
+| TBA_SZY_SJ_BASIN| | GX_AWQMD_BASE、GX_AWQMD_METAL、| 测站名称,时间(YYYY-MM),水温,PH值,溶解氧,高猛酸盐指数,氨氮,总磷,总氮,五日生化需氧量,化学需氧量,氰化物,氟化物,硫化物,阴离子表面活性剂,汞,铜,铅,镉,锌,砷,六价石油类、挥发酚、叶绿素a、透明度|
+|TBA_SYD_LZDC| |GX_AWQMD_BASE、GX_AWQMD_METAL、GX_AWQMD_AMR | 测站名称、时间(YYYY-MM-DD),水色、溶解氧、水温、PH、电导率、高锰酸盐指数、氨氮、总氮、总磷、溶解性总磷、叶绿素a、浮游植物数量、浮游植物生物量、风速、风向、蓝藻状况、蓝藻门细胞密度、隐藻门细胞密度、甲藻门细胞密度、金藻门细胞密度、硅藻门细胞密度、裸藻门细胞密度、绿藻门细胞密度|
+| TBA_SZY_RH| |GX_AWQMD_BASE、GX_AWQMD_METAL、GX_AWQMD_AMR | 测站名称,时间(YYYY-MM),水温,PH值,溶解氧,高猛酸盐指数,氨氮,总磷,总氮,五日生化需氧量,化学需氧量,氰化物,氟化物,硫化物,阴离子表面活性剂,汞,铜,铅,镉,锌,砷,六价石油类、挥发酚、叶绿素a、透明度|
+|TBA_SZY_LZJC|GX_AWQMD_BASE、GX_AWQMD_METAL、 GX_AWQMD_AMR| | 湖区、测站名称、时间(YYYY-MM),藻类植物生物量、主要优势种、蓝藻门细胞密度、隐藻门细胞密度、甲藻门细胞密度、金藻门细胞密度、硅藻门细胞密度、裸藻门细胞密度、绿藻门细胞密度|
+| TBA_SZ_1H2H| GX_AWQMD_BASE、GX_AWQMD_METAL、GX_AWQMD_AMR| | 工程(口门)、河流名称、测站名称,时间(YYYY-MM-DD HH:mm:ss),水温,PH值,电导率、溶解氧,高猛酸盐指数,氨氮,总磷,总氮,浊度、叶绿素a、氧化还原电位|
+| TBA_SLDD_GC|GX_RIVER_H | | 工程(口门)、时间(YYYY-MM-DD HH:mm),调度指令、goon工程运行状态、闸上水位、闸下水位、引水量、排水量、备注|
+| TBA_JYL_FQ| | | 时间(YYYY-MM),|
+| TBA_STSW_TH| GX_RIVER_H| | 河流名称、测站名称,时间(YYYY-MM-DD HH:mm:ss),水位|

+ 50 - 0
gw-admin/src/main/java/com/goldenwater/web/controller/gx/GxDataCommonController.java

@@ -0,0 +1,50 @@
+package com.goldenwater.web.controller.gx;
+
+import java.util.List;
+import java.util.Map;
+import com.goldenwater.common.annotation.Anonymous;
+import com.goldenwater.common.core.controller.BaseController;
+import com.goldenwater.common.core.domain.AjaxResult;
+import com.goldenwater.gx.service.IGxDataCommonService;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import jakarta.annotation.Resource;
+
+/**
+ * 通用数据查询Controller - DataCommon前端页面使用
+ */
+@RestController
+@RequestMapping("/gx/dataCommon")
+public class GxDataCommonController extends BaseController {
+
+    @Resource
+    private IGxDataCommonService gxDataCommonService;
+
+    /**
+     * 根据业务域查询最新数据
+     */
+    @Anonymous
+    @GetMapping("/front/latest/{bizCode}")
+    public AjaxResult frontLatest(@PathVariable String bizCode) {
+        return success(gxDataCommonService.selectLatestByBizCode(bizCode));
+    }
+
+    /**
+     * 查询历史数据
+     * @param bizCode 业务域编码
+     * @param stcd 站码(水质、水文数据使用)
+     * @param stnm 站点名称/行政区(污染源数据使用)
+     */
+    @Anonymous
+    @GetMapping("/front/history")
+    public AjaxResult frontHistory(
+            @RequestParam(value = "bizCode") String bizCode,
+            @RequestParam(value = "stcd", required = false) String stcd,
+            @RequestParam(value = "stnm", required = false) String stnm) {
+        return success(gxDataCommonService.selectHistoryByBizCodeAndStcd(bizCode, stcd, stnm));
+    }
+}

+ 10 - 0
gw-admin/src/main/java/com/goldenwater/web/controller/gx/GxExchangeStationController.java

@@ -126,4 +126,14 @@ public class GxExchangeStationController extends BaseController
     {
         return success(gxExchangeStationService.selectStationByBizCode(bizCode));
     }
+
+    /**
+     * 批量查询各业务域的站点数量(前端匿名访问)
+     */
+    @Anonymous
+    @GetMapping("/front/countByBizCodes")
+    public AjaxResult frontCountByBizCodes(@RequestParam(value = "bizCodes", required = false) List<String> bizCodes)
+    {
+        return success(gxExchangeStationService.countStationByBizCodes(bizCodes));
+    }
 }

+ 96 - 0
gw-gx/src/main/java/com/goldenwater/gx/domain/GxAwqmdAmr.java

@@ -0,0 +1,96 @@
+package com.goldenwater.gx.domain;
+
+import com.goldenwater.common.annotation.Excel;
+import com.goldenwater.common.core.domain.BaseEntity;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 藻类/浮游植物监测数据 gx_awqmd_amr
+ */
+@Data
+public class GxAwqmdAmr extends BaseEntity {
+
+    /** 站码 */
+    @Excel(name = "站码")
+    private String stcd;
+
+    /** 时间 */
+    @Excel(name = "采样时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date spt;
+
+    /** 业务域 */
+    @Excel(name = "业务域")
+    private String bizCode;
+
+    /** 风速 */
+    @Excel(name = "风速")
+    private BigDecimal wndv;
+
+    /** 风力 */
+    @Excel(name = "风力")
+    private BigDecimal wndpwr;
+
+    /** 风向 */
+    @Excel(name = "风向")
+    private String windir;
+
+    /** 浮游植物种类 */
+    @Excel(name = "浮游植物种类")
+    private String physp;
+
+    /** 浮游植物密度 */
+    @Excel(name = "浮游植物密度")
+    private BigDecimal phyd;
+
+    /** 蓝藻门细胞密度 */
+    @Excel(name = "蓝藻门细胞密度")
+    private BigDecimal cyncd;
+
+    /** 隐藻门细胞密度 */
+    @Excel(name = "隐藻门细胞密度")
+    private BigDecimal crycd;
+
+    /** 甲藻门细胞密度 */
+    @Excel(name = "甲藻门细胞密度")
+    private BigDecimal pyrcd;
+
+    /** 金藻门细胞密度 */
+    @Excel(name = "金藻门细胞密度")
+    private BigDecimal chrcd;
+
+    /** 黄藻门细胞密度 */
+    @Excel(name = "黄藻门细胞密度")
+    private BigDecimal xntcd;
+
+    /** 硅藻门细胞密度 */
+    @Excel(name = "硅藻门细胞密度")
+    private BigDecimal bclcd;
+
+    /** 裸藻门细胞密度 */
+    @Excel(name = "裸藻门细胞密度")
+    private BigDecimal eugcd;
+
+    /** 绿藻门细胞密度 */
+    @Excel(name = "绿藻门细胞密度")
+    private BigDecimal chlcd;
+
+    /** 浮游植物优势种密度 */
+    @Excel(name = "浮游植物优势种密度")
+    private String phydd;
+
+    /** 浮游植物多样性指数 */
+    @Excel(name = "浮游植物多样性指数")
+    private String phydi;
+
+    /** 浮游植物优势种 */
+    @Excel(name = "浮游植物优势种")
+    private String phys;
+
+    /** 备注 */
+    @Excel(name = "备注")
+    private String nt;
+
+}

+ 84 - 0
gw-gx/src/main/java/com/goldenwater/gx/domain/GxAwqmdBase.java

@@ -0,0 +1,84 @@
+package com.goldenwater.gx.domain;
+
+import com.goldenwater.common.annotation.Excel;
+import com.goldenwater.common.core.domain.BaseEntity;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 水质监测基础数据 gx_awqmd_base
+ */
+@Data
+public class GxAwqmdBase extends BaseEntity {
+
+    /** 测站编码 */
+    @Excel(name = "测站编码")
+    private String stcd;
+
+    /** 采样时间 */
+    @Excel(name = "采样时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date spt;
+
+    /** 业务域编码 */
+    @Excel(name = "业务域")
+    private String bizCode;
+
+    /** 水温 */
+    @Excel(name = "水温")
+    private BigDecimal wt;
+
+    /** pH值 */
+    @Excel(name = "PH值")
+    private BigDecimal ph;
+
+    /** 溶解氧 */
+    @Excel(name = "溶解氧")
+    private BigDecimal dox;
+
+    /** 高锰酸盐指数 */
+    @Excel(name = "高锰酸盐指数")
+    private BigDecimal codmn;
+
+    /** 化学需氧量 */
+    @Excel(name = "化学需氧量")
+    private BigDecimal codcr;
+
+    /** 五日生化需氧量 */
+    @Excel(name = "五日生化需氧量")
+    private BigDecimal bod5;
+
+    /** 氨氮 */
+    @Excel(name = "氨氮")
+    private BigDecimal nh3n;
+
+    /** 总磷 */
+    @Excel(name = "总磷")
+    private BigDecimal tp;
+
+    /** 总氮 */
+    @Excel(name = "总氮")
+    private BigDecimal tn;
+
+    /** 浊度 */
+    @Excel(name = "浊度")
+    private BigDecimal turb;
+
+    /** 电导率 */
+    @Excel(name = "电导率")
+    private BigDecimal cond;
+
+    /** 水温备注 */
+    @Excel(name = "水温备注")
+    private String wtExt;
+
+    /** pH备注 */
+    @Excel(name = "PH备注")
+    private String phExt;
+
+    /** 溶解氧备注 */
+    @Excel(name = "溶解氧备注")
+    private String doxExt;
+
+}

+ 116 - 0
gw-gx/src/main/java/com/goldenwater/gx/domain/GxAwqmdMetal.java

@@ -0,0 +1,116 @@
+package com.goldenwater.gx.domain;
+
+import com.goldenwater.common.annotation.Excel;
+import com.goldenwater.common.core.domain.BaseEntity;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 水质金属监测数据 gx_awqmd_metal
+ */
+@Data
+public class GxAwqmdMetal extends BaseEntity {
+
+    /** 测站编码 */
+    @Excel(name = "测站编码")
+    private String stcd;
+
+    /** 采样时间 */
+    @Excel(name = "采样时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date spt;
+
+    /** 业务域编码 */
+    @Excel(name = "业务域")
+    private String bizCode;
+
+    /** 铜 */
+    @Excel(name = "铜")
+    private BigDecimal cu;
+
+    /** 锌 */
+    @Excel(name = "锌")
+    private BigDecimal zn;
+
+    /** 氟化物 */
+    @Excel(name = "氟化物")
+    private BigDecimal f;
+
+    /** 硒 */
+    @Excel(name = "硒")
+    private BigDecimal se;
+
+    /** 砷 */
+    @Excel(name = "砷")
+    private BigDecimal ars;
+
+    /** 汞 */
+    @Excel(name = "汞")
+    private BigDecimal hg;
+
+    /** 镉 */
+    @Excel(name = "镉")
+    private BigDecimal cd;
+
+    /** 六价铬 */
+    @Excel(name = "六价铬")
+    private BigDecimal cr6;
+
+    /** 铅 */
+    @Excel(name = "铅")
+    private BigDecimal pb;
+
+    /** 氰化物 */
+    @Excel(name = "氰化物")
+    private BigDecimal cn;
+
+    /** 铁 */
+    @Excel(name = "铁")
+    private BigDecimal fe;
+
+    /** 锰 */
+    @Excel(name = "锰")
+    private BigDecimal mn;
+
+    /** 镍 */
+    @Excel(name = "镍")
+    private BigDecimal ni;
+
+    /** 硼 */
+    @Excel(name = "硼")
+    private BigDecimal b;
+
+    /** 钼 */
+    @Excel(name = "钼")
+    private BigDecimal mo;
+
+    /** 钴 */
+    @Excel(name = "钴")
+    private BigDecimal co;
+
+    /** 铍 */
+    @Excel(name = "铍")
+    private BigDecimal be;
+
+    /** 锑 */
+    @Excel(name = "锑")
+    private BigDecimal sb;
+
+    /** 钡 */
+    @Excel(name = "钡")
+    private BigDecimal ba;
+
+    /** 钒 */
+    @Excel(name = "钒")
+    private BigDecimal v;
+
+    /** 钛 */
+    @Excel(name = "钛")
+    private BigDecimal ti;
+
+    /** 铊 */
+    @Excel(name = "铊")
+    private BigDecimal tl;
+
+}

+ 59 - 0
gw-gx/src/main/java/com/goldenwater/gx/domain/GxRiverH.java

@@ -0,0 +1,59 @@
+package com.goldenwater.gx.domain;
+
+import com.goldenwater.common.annotation.Excel;
+import com.goldenwater.common.core.domain.BaseEntity;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 河道水文监测数据 gx_river_h
+ */
+@Data
+public class GxRiverH extends BaseEntity {
+
+    /** 主键ID */
+    private Long id;
+
+    /** 业务域编码 */
+    @Excel(name = "业务域编码")
+    private String bizCode;
+
+    /** 测站编码 */
+    @Excel(name = "测站编码")
+    private String stcd;
+
+    /** 监测时间 */
+    @Excel(name = "监测时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date spt;
+
+    /** 上水位 */
+    @Excel(name = "上水位")
+    private BigDecimal upz;
+
+    /** 下水位 */
+    @Excel(name = "下水位")
+    private BigDecimal dwz;
+
+    /** 流量 */
+    @Excel(name = "流量")
+    private BigDecimal q;
+
+    /** 引水量 */
+    @Excel(name = "引水量")
+    private BigDecimal ysl;
+
+    /** 排水量 */
+    @Excel(name = "排水量")
+    private BigDecimal psl;
+
+    /** 调度指令 */
+    @Excel(name = "调度指令")
+    private String ddzl;
+
+    /** 运行状态 */
+    @Excel(name = "运行状态")
+    private String yxzt;
+
+}

+ 31 - 9
gw-gx/src/main/java/com/goldenwater/gx/domain/GxStWry.java

@@ -1,28 +1,50 @@
 package com.goldenwater.gx.domain;
 
-import java.io.Serializable;
-
+import com.goldenwater.common.annotation.Excel;
 import com.goldenwater.common.core.domain.BaseEntity;
 import lombok.Data;
 
+import java.math.BigDecimal;
+
+/**
+ * 污染源数据 gx_st_wry
+ * 复合主键:bizCode + stcd + year
+ */
 @Data
 public class GxStWry extends BaseEntity {
 
     private static final long serialVersionUID = 1L;
 
-    private Long id;
-
+    /** 业务域编码 */
+    @Excel(name = "业务域编码")
     private String bizCode;
 
+    /** 行政区编码 */
+    @Excel(name = "行政区编码")
+    private String stcd;
+
+    /** 行政区名称 */
+    @Excel(name = "行政区名称")
     private String stnm;
 
-    private Integer year;
+    /** 年份(字符串) */
+    @Excel(name = "年份")
+    private String year;
+
+    /** 化学需氧量排放总量(吨) */
+    @Excel(name = "COD排放量")
+    private BigDecimal cod;
 
-    private Double cod;
+    /** 氨氮排放总量(吨) */
+    @Excel(name = "氨氮排放量")
+    private BigDecimal nh3;
 
-    private Double nh3;
+    /** 总氮排放总量(吨) */
+    @Excel(name = "总氮排放量")
+    private BigDecimal tn;
 
-    private Double tn;
+    /** 总磷排放总量(吨) */
+    @Excel(name = "总磷排放量")
+    private BigDecimal tp;
 
-    private Double tp;
 }

+ 29 - 0
gw-gx/src/main/java/com/goldenwater/gx/mapper/GxDataCommonMapper.java

@@ -0,0 +1,29 @@
+package com.goldenwater.gx.mapper;
+
+import org.apache.ibatis.annotations.Param;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 通用数据查询Mapper - 用于DataCommon页面多表联合查询
+ */
+public interface GxDataCommonMapper {
+
+    /**
+     * 根据业务域类型查询最新数据
+     * bizType: 
+     *   - RIVER_H: GX_RIVER_H 水位流量数据
+     *   - AWQMD: GX_AWQMD_BASE + METAL + AMR 水质数据
+     *   - WRY: GX_ST_WRY 污染源数据
+     */
+    List<Map<String, Object>> selectLatestByBizType(@Param("bizCode") String bizCode, 
+                                                     @Param("bizType") String bizType);
+
+    /**
+     * 根据业务域和站点查询历史数据
+     */
+    List<Map<String, Object>> selectHistoryByBizTypeAndStcd(@Param("bizCode") String bizCode,
+                                                              @Param("bizType") String bizType,
+                                                              @Param("stcd") String stcd,
+                                                              @Param("stnm") String stnm);
+}

+ 6 - 0
gw-gx/src/main/java/com/goldenwater/gx/mapper/GxExchangeStationMapper.java

@@ -1,6 +1,7 @@
 package com.goldenwater.gx.mapper;
 
 import java.util.List;
+import java.util.Map;
 import org.apache.ibatis.annotations.Param;
 import com.goldenwater.gx.domain.GxExchangeStation;
 
@@ -33,4 +34,9 @@ public interface GxExchangeStationMapper
      * 联合主键:STCD + BIZ_CODE + STNM
      */
     public GxExchangeStation selectStationByCompositeKey(@Param("stcd") String stcd, @Param("bizCode") String bizCode, @Param("stnm") String stnm);
+
+    /**
+     * 批量查询各业务域的站点数量
+     */
+    public List<Map<String, Object>> countStationGroupByBizCode(@Param("bizCodes") List<String> bizCodes);
 }

+ 28 - 0
gw-gx/src/main/java/com/goldenwater/gx/service/IGxDataCommonService.java

@@ -0,0 +1,28 @@
+package com.goldenwater.gx.service;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 通用数据查询Service接口
+ */
+public interface IGxDataCommonService {
+
+    /**
+     * 根据业务域查询最新数据
+     */
+    List<Map<String, Object>> selectLatestByBizCode(String bizCode);
+
+    /**
+     * 根据业务域和站码查询历史数据
+     */
+    List<Map<String, Object>> selectHistoryByBizCodeAndStcd(String bizCode, String stcd, String stnm);
+
+    /**
+     * 判断业务域类型
+     * RIVER_H: 水文水位流量
+     * AWQMD: 水质监测
+     * WRY: 污染源
+     */
+    String getBizType(String bizCode);
+}

+ 6 - 0
gw-gx/src/main/java/com/goldenwater/gx/service/IGxExchangeStationService.java

@@ -1,6 +1,7 @@
 package com.goldenwater.gx.service;
 
 import java.util.List;
+import java.util.Map;
 import com.goldenwater.gx.domain.GxExchangeStation;
 
 /**
@@ -31,4 +32,9 @@ public interface IGxExchangeStationService
      * @return 结果信息
      */
     public String importStation(List<GxExchangeStation> stationList, Boolean updateSupport, String operName);
+
+    /**
+     * 批量查询各业务域的站点数量
+     */
+    public Map<String, Integer> countStationByBizCodes(List<String> bizCodes);
 }

+ 78 - 0
gw-gx/src/main/java/com/goldenwater/gx/service/impl/GxDataCommonServiceImpl.java

@@ -0,0 +1,78 @@
+package com.goldenwater.gx.service.impl;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.goldenwater.gx.mapper.GxDataCommonMapper;
+import com.goldenwater.gx.service.IGxDataCommonService;
+
+@Service
+public class GxDataCommonServiceImpl implements IGxDataCommonService {
+
+    private static final Logger log = LoggerFactory.getLogger(GxDataCommonServiceImpl.class);
+
+    @Autowired
+    private GxDataCommonMapper gxDataCommonMapper;
+
+    // 水文类型的业务域(GX_RIVER_H 表)
+    private static final List<String> RIVER_H_BIZ_CODES = Arrays.asList(
+        "TBA_SW_1H2H", "TBA_SLDD_GC", "TBA_STSW_TH"
+    );
+
+    // 水质类型的业务域(GX_AWQMD_BASE + METAL + AMR 三表联合)
+    private static final List<String> AWQMD_BIZ_CODES = Arrays.asList(
+        "TBA_SZY_SJ_RIVER", "TBA_SZY_SJ_BASIN", "TBA_SYD_LZDC",
+        "TBA_SZY_RH", "TBA_SZY_LZJC", "TBA_SZ_1H2H"
+    );
+
+    // 污染源类型的业务域(GX_ST_WRY 表)
+    private static final List<String> WRY_BIZ_CODES = Arrays.asList(
+        "JS_ST_WRY", "ZJ_ST_WRY", "SH_ST_WRY"
+    );
+
+    @Override
+    public String getBizType(String bizCode) {
+        if (bizCode == null) {
+            return "DEFAULT";
+        }
+        if (RIVER_H_BIZ_CODES.contains(bizCode)) {
+            return "RIVER_H";
+        }
+        if (AWQMD_BIZ_CODES.contains(bizCode)) {
+            return "AWQMD";
+        }
+        if (WRY_BIZ_CODES.contains(bizCode)) {
+            return "WRY";
+        }
+        return "DEFAULT";
+    }
+
+    @Override
+    public List<Map<String, Object>> selectLatestByBizCode(String bizCode) {
+        String bizType = getBizType(bizCode);
+        log.info("查询业务域[{}]最新数据,类型:{}", bizCode, bizType);
+        try {
+            return gxDataCommonMapper.selectLatestByBizType(bizCode, bizType);
+        } catch (Exception e) {
+            log.warn("查询业务域[{}]最新数据失败(表可能不存在):{}", bizCode, e.getMessage());
+            return new ArrayList<>();
+        }
+    }
+
+    @Override
+    public List<Map<String, Object>> selectHistoryByBizCodeAndStcd(String bizCode, String stcd, String stnm) {
+        String bizType = getBizType(bizCode);
+        log.info("查询业务域[{}]站点[{}]历史数据,类型:{}", bizCode, stcd, bizType);
+        try {
+            return gxDataCommonMapper.selectHistoryByBizTypeAndStcd(bizCode, bizType, stcd, stnm);
+        } catch (Exception e) {
+            log.warn("查询业务域[{}]站点[{}]历史数据失败(表可能不存在):{}", bizCode, stcd, e.getMessage());
+            return new ArrayList<>();
+        }
+    }
+}

+ 38 - 0
gw-gx/src/main/java/com/goldenwater/gx/service/impl/GxExchangeStationServiceImpl.java

@@ -1,6 +1,8 @@
 package com.goldenwater.gx.service.impl;
 
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -162,4 +164,40 @@ public class GxExchangeStationServiceImpl implements IGxExchangeStationService
         }
         return resultMsg.toString();
     }
+
+    @Override
+    public Map<String, Integer> countStationByBizCodes(List<String> bizCodes)
+    {
+        Map<String, Integer> result = new HashMap<>();
+        if (bizCodes == null || bizCodes.isEmpty())
+        {
+            return result;
+        }
+        try
+        {
+            List<Map<String, Object>> list = gxExchangeStationMapper.countStationGroupByBizCode(bizCodes);
+            if (list != null)
+            {
+                for (Map<String, Object> item : list)
+                {
+                    String bizCode = (String) item.get("bizCode");
+                    if (bizCode == null)
+                    {
+                        bizCode = (String) item.get("BIZCODE");
+                    }
+                    Number cnt = (Number) item.get("cnt");
+                    if (cnt == null)
+                    {
+                        cnt = (Number) item.get("CNT");
+                    }
+                    result.put(bizCode, cnt != null ? cnt.intValue() : 0);
+                }
+            }
+        }
+        catch (Exception e)
+        {
+            log.warn("批量查询站点数量失败: {}", e.getMessage());
+        }
+        return result;
+    }
 }

+ 140 - 0
gw-gx/src/main/resources/mapper/gx/GxDataCommonMapper.xml

@@ -0,0 +1,140 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.goldenwater.gx.mapper.GxDataCommonMapper">
+
+    <!-- 按业务域类型查询最新数据 -->
+    <select id="selectLatestByBizType" resultType="java.util.HashMap">
+        <choose>
+            <!-- ========== 1. RIVER_H 水文数据 ========== -->
+            <when test="bizType == 'RIVER_H'">
+                SELECT
+                    r.stcd, s.stnm, s.rvnm, s.hnnm,
+                    r.spt, r.upz, r.dwz, r.q, r.ysl, r.psl,
+                    r.ddzl, r.yxzt,
+                    s.unit_code AS unitCode
+                FROM gx_river_h r
+                LEFT JOIN gx_exchange_station s ON r.stcd = s.stcd AND s.del_flag = '0'
+                WHERE r.biz_code = #{bizCode}
+                  AND r.spt = (
+                      SELECT MAX(r2.spt) FROM gx_river_h r2
+                      WHERE r2.biz_code = #{bizCode} AND r2.stcd = r.stcd
+                  )
+                ORDER BY r.stcd
+            </when>
+
+            <!-- ========== 2. WRY 污染源数据 ========== -->
+            <when test="bizType == 'WRY'">
+                SELECT w.biz_code, w.stcd, w.stnm, w.year, w.cod, w.nh3, w.tn, w.tp
+                FROM gx_st_wry w
+                WHERE w.biz_code = #{bizCode}
+                  AND w.year = (
+                      SELECT MAX(w2.year) FROM gx_st_wry w2
+                      WHERE w2.biz_code = #{bizCode} AND w2.stnm = w.stnm
+                  )
+                ORDER BY w.cod DESC
+            </when>
+
+            <!-- ========== 3. AWQMD 水质数据 (BASE + METAL + AMR 三表联合) ========== -->
+            <when test="bizType == 'AWQMD'">
+                SELECT
+                    b.stcd, s.stnm, s.rvnm, b.spt,
+                    b.wt, b.ph, b.cond, b.dox, b.codmn, b.codcr, b.bod5, b.nh3n,
+                    b.tp, b.tn, b.turb,
+                    b.wt_ext, b.ph_ext, b.dox_ext,
+                    m.cu, m.zn, m.f, m.se, m.ars, m.hg, m.cd, m.cr6, m.pb, m.cn,
+                    m.fe, m.mn, m.ni, m.b, m.mo, m.co, m.be, m.sb, m.ba, m.v, m.ti, m.tl,
+                    a.wndv, a.wndpwr, a.windir,
+                    a.physp, a.phyd,
+                    a.cyncd, a.crycd, a.pyrcd, a.chrcd, a.xntcd, a.bclcd, a.eugcd, a.chlcd,
+                    a.phydd, a.phydi, a.phys,
+                    a.nt,
+                    a.biz_code AS amrBizCode,
+                    s.unit_code AS unitCode
+                FROM gx_awqmd_base b
+                LEFT JOIN gx_exchange_station s ON b.stcd = s.stcd AND s.del_flag = '0'
+                LEFT JOIN gx_awqmd_metal m ON b.stcd = m.stcd AND b.spt = m.spt AND b.biz_code = m.biz_code
+                LEFT JOIN gx_awqmd_amr   a ON b.stcd = a.stcd AND b.spt = a.spt AND b.biz_code = a.biz_code
+                WHERE b.biz_code = #{bizCode}
+                  AND b.spt = (
+                      SELECT MAX(b2.spt) FROM gx_awqmd_base b2
+                      WHERE b2.biz_code = #{bizCode} AND b2.stcd = b.stcd
+                  )
+                ORDER BY b.stcd
+            </when>
+
+            <!-- ========== 默认:回退到站点表+质量评估 ========== -->
+            <otherwise>
+                SELECT
+                    s.stcd, s.stnm, s.rvnm, s.hnnm, s.bsnm, s.stlc, s.lgtd, s.lttd,
+                    s.biz_code, s.unit_code,
+                    (SELECT MAX(q.analysis_date) FROM gx_exchange_quality q
+                     WHERE q.stcd = s.stcd AND q.del_flag = '0') AS spt,
+                    (SELECT TOP 1 q.value FROM gx_exchange_quality q
+                     WHERE q.stcd = s.stcd AND q.del_flag = '0' ORDER BY q.analysis_date DESC) AS val
+                FROM gx_exchange_station s
+                WHERE s.biz_code = #{bizCode} AND s.del_flag = '0'
+                ORDER BY s.stcd
+            </otherwise>
+        </choose>
+    </select>
+
+    <!-- 按业务域类型+站码查询历史数据 -->
+    <select id="selectHistoryByBizTypeAndStcd" resultType="java.util.HashMap">
+        <choose>
+            <!-- 1. RIVER_H 历史 -->
+            <when test="bizType == 'RIVER_H'">
+                SELECT TOP 100
+                    r.stcd, s.stnm, s.rvnm,
+                    r.spt, r.upz, r.dwz, r.q, r.ysl, r.psl, r.ddzl, r.yxzt
+                FROM gx_river_h r
+                LEFT JOIN gx_exchange_station s ON r.stcd = s.stcd AND s.del_flag = '0'
+                WHERE r.biz_code = #{bizCode}
+                  AND r.stcd = #{stcd}
+                ORDER BY r.spt DESC
+            </when>
+
+            <!-- 2. WRY 历史 -->
+            <when test="bizType == 'WRY'">
+                SELECT w.biz_code, w.stcd, w.stnm, w.year, w.cod, w.nh3, w.tn, w.tp
+                FROM gx_st_wry w
+                WHERE w.biz_code = #{bizCode}
+                  AND w.stnm = #{stnm}
+                ORDER BY w.year DESC
+            </when>
+
+            <!-- 3. AWQMD 历史 -->
+            <when test="bizType == 'AWQMD'">
+                SELECT TOP 100
+                    b.stcd, s.stnm, s.rvnm, b.spt,
+                    b.wt, b.ph, b.cond, b.dox, b.codmn, b.codcr, b.bod5, b.nh3n,
+                    b.tp, b.tn, b.turb,
+                    b.wt_ext, b.ph_ext, b.dox_ext,
+                    m.cu, m.zn, m.f, m.se, m.ars, m.hg, m.cd, m.cr6, m.pb, m.cn,
+                    m.fe, m.mn, m.ni, m.b, m.mo, m.co, m.be, m.sb, m.ba, m.v, m.ti, m.tl,
+                    a.wndv, a.wndpwr, a.windir,
+                    a.physp, a.phyd,
+                    a.cyncd, a.crycd, a.pyrcd, a.chrcd, a.xntcd, a.bclcd, a.eugcd, a.chlcd,
+                    a.phydd, a.phydi, a.phys
+                FROM gx_awqmd_base b
+                LEFT JOIN gx_exchange_station s ON b.stcd = s.stcd AND s.del_flag = '0'
+                LEFT JOIN gx_awqmd_metal m ON b.stcd = m.stcd AND b.spt = m.spt AND b.biz_code = m.biz_code
+                LEFT JOIN gx_awqmd_amr   a ON b.stcd = a.stcd AND b.spt = a.spt AND b.biz_code = a.biz_code
+                WHERE b.biz_code = #{bizCode}
+                  AND b.stcd = #{stcd}
+                ORDER BY b.spt DESC
+            </when>
+
+            <!-- 默认:质量评估历史 -->
+            <otherwise>
+                SELECT TOP 100
+                    q.id, q.biz_code, q.stcd, q.analysis_date AS spt,
+                    q.type, q.value, q.cnt, q.description, q.starttime, q.endtime
+                FROM gx_exchange_quality q
+                WHERE q.stcd = #{stcd}
+                  AND q.del_flag = '0'
+                ORDER BY q.analysis_date DESC
+            </otherwise>
+        </choose>
+    </select>
+
+</mapper>

+ 11 - 0
gw-gx/src/main/resources/mapper/gx/GxExchangeStationMapper.xml

@@ -165,4 +165,15 @@
         </foreach>
     </update>
 
+    <select id="countStationGroupByBizCode" resultType="java.util.HashMap">
+        SELECT biz_code AS bizCode, COUNT(*) AS cnt
+        FROM gx_exchange_station
+        WHERE del_flag = '0'
+          AND biz_code IN
+        <foreach item="code" collection="bizCodes" open="(" separator="," close=")">
+            #{code}
+        </foreach>
+        GROUP BY biz_code
+    </select>
+
 </mapper>

+ 533 - 240
gw-ui/src/views/front/DataCommon.vue

@@ -1,6 +1,7 @@
 <template>
   <div class="page-container">
     <div class="page-content">
+      <!-- 搜索栏 -->
       <div class="search-bar">
         <div class="search-left">
           <el-select v-model="selectedUnit" placeholder="请选择共享单位" @change="handleUnitChange" class="unit-select">
@@ -9,8 +10,8 @@
         </div>
         <div class="search-center">
           <div class="biz-buttons">
-            <el-button 
-              v-for="biz in bizList" 
+            <el-button
+              v-for="biz in bizList"
               :key="biz.bizCode"
               :class="{ active: selectedBiz === biz.bizCode }"
               @click="handleBizClick(biz.bizCode)"
@@ -22,42 +23,36 @@
           </div>
         </div>
         <div class="search-right">
-          <el-input v-model="searchKeyword" placeholder="请输入站名/行政区名" @keyup.enter="loadData" clearable style="width: 200px">
+          <el-input v-model="searchKeyword" placeholder="请输入站名/站码/行政区名" @keyup.enter="loadData" clearable style="width: 240px">
             <template #prefix>
               <el-icon><Search /></el-icon>
             </template>
           </el-input>
         </div>
       </div>
-      
+
+      <!-- 数据表格 -->
       <div class="table-container">
-        <el-table :data="tableData" stripe border v-loading="loading" @row-click="handleRowClick">
-          <template v-if="isWryType">
-            <el-table-column type="index" label="序号" width="60" align="center" />
-            <el-table-column prop="stnm" label="行政区" width="250" />
-            <el-table-column prop="year" label="年份" width="80" />
-            <el-table-column prop="cod" label="化学需氧量排放总量(吨)" width="200" />
-            <el-table-column prop="nh3" label="氨氮排放总量(吨)" width="200" />
-            <el-table-column prop="tn" label="总氮排放总量(吨)" width="200" />
-            <el-table-column prop="tp" label="总磷排放总量(吨)" width="200" />
-          </template>
-          <template v-else>
-            <el-table-column type="index" label="序号" width="60" align="center" />
-            <el-table-column prop="stcd" label="站码" width="100" />
-            <el-table-column prop="stnm" label="站名" width="150" />
-            <el-table-column prop="rvnm" label="河流名称" width="200" />
-            <el-table-column prop="latestDataTime" label="最新数据时间" width="170" />
-            <el-table-column prop="wt" label="水温(℃)" width="100" />
-            <el-table-column prop="ph" label="pH" width="80" />
-            <el-table-column prop="do" label="溶解氧(mg/L)" width="110" />
-            <el-table-column prop="nh3n" label="氨氮(mg/L)" width="100" />
-            <el-table-column prop="tp" label="总磷(mg/L)" width="100" />
-            <el-table-column prop="tn" label="总氮(mg/L)" width="100" />
-            <el-table-column prop="cod" label="COD(mg/L)" width="100" />
-            <el-table-column prop="bod" label="BOD(mg/L)" width="100" />
+        <el-table :data="tableData" stripe border v-loading="loading" @row-click="handleRowClick" height="100%" max-height="100%">
+          <el-table-column type="index" label="序号" width="60" align="center" fixed="left" />
+          <template v-for="col in currentColumns" :key="col.prop">
+            <el-table-column
+              :prop="col.prop"
+              :label="col.label"
+              :width="col.width || 140"
+              :min-width="col.minWidth"
+              :formatter="col.formatter"
+              :align="col.align || 'center'"
+              show-overflow-tooltip
+            />
           </template>
+          <el-table-column label="操作" width="90" fixed="right" align="center">
+            <template #default="{ row }">
+              <el-button type="primary" link @click.stop="handleRowClick(row)">历史数据</el-button>
+            </template>
+          </el-table-column>
         </el-table>
-        
+
         <div v-if="tableData.length === 0 && !loading" class="empty-state">
           <el-icon class="empty-icon"><DataAnalysis /></el-icon>
           <p>暂无数据</p>
@@ -65,18 +60,33 @@
       </div>
     </div>
 
-    <el-dialog :title="historyDialogTitle" v-model="historyDialogVisible" width="900px" append-to-body>
+    <!-- 历史数据弹框 -->
+    <el-dialog :title="historyDialogTitle" v-model="historyDialogVisible" width="85%" append-to-body top="5vh">
       <div class="history-dialog-content">
         <div class="history-table">
-          <el-table :data="historyTableData" stripe border>
-            <el-table-column prop="year" label="年份" width="100" />
-            <el-table-column prop="cod" label="化学需氧量排放总量(吨)" width="200" />
-            <el-table-column prop="nh3" label="氨氮排放总量(吨)" width="200" />
-            <el-table-column prop="tn" label="总氮排放总量(吨)" width="200" />
-            <el-table-column prop="tp" label="总磷排放总量(吨)" width="200" />
+          <el-table :data="historyTableData" stripe border height="100%" max-height="560">
+            <el-table-column type="index" label="序号" width="60" align="center" fixed="left" />
+            <template v-for="col in historyColumns" :key="col.prop">
+              <el-table-column
+                :prop="col.prop"
+                :label="col.label"
+                :width="col.width || 140"
+                :formatter="col.formatter"
+                :align="col.align || 'center'"
+                show-overflow-tooltip
+              />
+            </template>
           </el-table>
         </div>
         <div class="history-chart">
+          <div class="chart-title">
+            <span>历史趋势图</span>
+            <el-select v-model="chartMetrics" multiple collapse-tags collapse-tags-tooltip
+                       placeholder="请选择指标" style="width: 420px; display: inline-block; vertical-align: middle; margin-left: 15px;"
+                       @change="renderChart()">
+              <el-option v-for="m in chartableMetrics" :key="m.prop" :label="m.label" :value="m.prop" />
+            </el-select>
+          </div>
           <div ref="chartRef" class="chart-container"></div>
         </div>
       </div>
@@ -85,13 +95,14 @@
 </template>
 
 <script setup>
-import { ref, onMounted, computed, nextTick, watch } from 'vue'
+import { ref, onMounted, nextTick, watch, computed } from 'vue'
 import { Search, DataAnalysis } from '@element-plus/icons-vue'
 import request from '@/utils/request'
 import * as echarts from 'echarts'
 
+// ================ 基础变量 ================
 const selectedUnit = ref('TBA')
-const selectedBiz = ref('TBA_SW_1H2H')
+const selectedBiz = ref('')
 const searchKeyword = ref('')
 const loading = ref(false)
 
@@ -103,14 +114,329 @@ const historyDialogVisible = ref(false)
 const historyDialogTitle = ref('')
 const historyTableData = ref([])
 const chartRef = ref(null)
+const chartMetrics = ref([])
 let chartInstance = null
 
-const WRY_TYPES = ['JS_ST_WRY', 'ZJ_ST_WRY', 'SH_ST_WRY']
+// ================ 业务域字段配置 ================
+// 按业务域配置:表格列、图表可选项(注意 prop 对应 HashMap 默认小写 key)
+const BIZ_COLUMN_CONFIG = {
+  // ========== 1. 水位流量类 - RIVER_H 表 ==========
+  TBA_SW_1H2H: {
+    type: 'RIVER_H',
+    columns: [
+      { prop: 'stcd', label: '站码', width: 100 },
+      { prop: 'stnm', label: '站点名称', width: 160 },
+      { prop: 'rvnm', label: '河流名称', width: 160 },
+      { prop: 'spt', label: '监测时间', width: 170, formatter: fmtDateTime },
+      { prop: 'upz', label: '上水位(m)', width: 110 },
+      { prop: 'dwz', label: '下水位(m)', width: 110 },
+      { prop: 'q', label: '流量(m³/s)', width: 120 }
+    ],
+    historyChart: ['upz', 'dwz', 'q']
+  },
+  TBA_SLDD_GC: {
+    type: 'RIVER_H',
+    columns: [
+      { prop: 'stcd', label: '站码', width: 100 },
+      { prop: 'stnm', label: '工程(口门)', width: 180 },
+      { prop: 'spt', label: '监测时间', width: 170, formatter: fmtDateTime },
+      { prop: 'upz', label: '闸上水位(m)', width: 120 },
+      { prop: 'dwz', label: '闸下水位(m)', width: 120 },
+      { prop: 'q', label: '流量', width: 100 },
+      { prop: 'ysl', label: '引水量', width: 100 },
+      { prop: 'psl', label: '排水量', width: 100 },
+      { prop: 'ddzl', label: '调度指令', width: 160 },
+      { prop: 'yxzt', label: '运行状态', width: 100 }
+    ],
+    historyChart: ['upz', 'dwz', 'ysl', 'psl']
+  },
+  TBA_STSW_TH: {
+    type: 'RIVER_H',
+    columns: [
+      { prop: 'rvnm', label: '河流名称', width: 160 },
+      { prop: 'stcd', label: '站码', width: 100 },
+      { prop: 'stnm', label: '测站名称', width: 160 },
+      { prop: 'spt', label: '监测时间', width: 170, formatter: fmtDateTime },
+      { prop: 'upz', label: '上水位(m)', width: 110 },
+      { prop: 'dwz', label: '下水位(m)', width: 110 },
+      { prop: 'q', label: '流量(m³/s)', width: 120 }
+    ],
+    historyChart: ['upz', 'dwz', 'q']
+  },
+
+  // ========== 2. 水质基础类 - AWQMD (BASE + METAL + AMR) ==========
+  TBA_SZY_SJ_RIVER: {
+    type: 'AWQMD',
+    columns: [
+      { prop: 'stcd', label: '站码', width: 100 },
+      { prop: 'stnm', label: '测站名称', width: 160 },
+      { prop: 'rvnm', label: '河流', width: 140 },
+      { prop: 'spt', label: '采样时间', width: 170, formatter: fmtDateTime },
+      { prop: 'wt', label: '水温(℃)', width: 90 },
+      { prop: 'ph', label: 'PH值', width: 80 },
+      { prop: 'cond', label: '电导率', width: 90 },
+      { prop: 'dox', label: '溶解氧', width: 90 },
+      { prop: 'codmn', label: '高锰酸盐指数', width: 120 },
+      { prop: 'codcr', label: '化学需氧量', width: 110 },
+      { prop: 'bod5', label: '五日生化需氧量', width: 130 },
+      { prop: 'nh3n', label: '氨氮', width: 90 },
+      { prop: 'tp', label: '总磷', width: 90 },
+      { prop: 'tn', label: '总氮', width: 90 },
+      { prop: 'turb', label: '浊度', width: 90 },
+      { prop: 'cu', label: '铜', width: 70 },
+      { prop: 'zn', label: '锌', width: 70 },
+      { prop: 'f', label: '氟化物', width: 80 },
+      { prop: 'se', label: '硒', width: 70 },
+      { prop: 'ars', label: '砷', width: 70 },
+      { prop: 'hg', label: '汞', width: 70 },
+      { prop: 'cd', label: '镉', width: 70 },
+      { prop: 'cr6', label: '六价铬', width: 80 },
+      { prop: 'pb', label: '铅', width: 70 },
+      { prop: 'cn', label: '氰化物', width: 80 },
+      { prop: 'fe', label: '铁', width: 70 },
+      { prop: 'mn', label: '锰', width: 70 },
+      { prop: 'ni', label: '镍', width: 70 },
+      { prop: 'b', label: '硼', width: 70 },
+      { prop: 'mo', label: '钼', width: 70 },
+      { prop: 'co', label: '钴', width: 70 },
+      { prop: 'be', label: '铍', width: 70 },
+      { prop: 'sb', label: '锑', width: 70 },
+      { prop: 'ba', label: '钡', width: 70 },
+      { prop: 'v', label: '钒', width: 70 },
+      { prop: 'ti', label: '钛', width: 70 },
+      { prop: 'tl', label: '铊', width: 70 }
+    ],
+    historyChart: ['wt', 'ph', 'dox', 'codmn', 'codcr', 'bod5', 'nh3n', 'tp', 'tn', 'cond']
+  },
+  TBA_SZY_SJ_BASIN: {
+    type: 'AWQMD',
+    columns: [
+      { prop: 'stcd', label: '站码', width: 100 },
+      { prop: 'stnm', label: '测站名称', width: 160 },
+      { prop: 'rvnm', label: '河流', width: 140 },
+      { prop: 'spt', label: '采样时间', width: 170, formatter: fmtDateTime },
+      { prop: 'wt', label: '水温(℃)', width: 90 },
+      { prop: 'ph', label: 'PH值', width: 80 },
+      { prop: 'cond', label: '电导率', width: 90 },
+      { prop: 'dox', label: '溶解氧', width: 90 },
+      { prop: 'codmn', label: '高锰酸盐指数', width: 120 },
+      { prop: 'codcr', label: '化学需氧量', width: 110 },
+      { prop: 'bod5', label: '五日生化需氧量', width: 130 },
+      { prop: 'nh3n', label: '氨氮', width: 90 },
+      { prop: 'tp', label: '总磷', width: 90 },
+      { prop: 'tn', label: '总氮', width: 90 },
+      { prop: 'turb', label: '浊度', width: 90 },
+      { prop: 'cu', label: '铜', width: 70 },
+      { prop: 'zn', label: '锌', width: 70 },
+      { prop: 'f', label: '氟化物', width: 80 },
+      { prop: 'ars', label: '砷', width: 70 },
+      { prop: 'hg', label: '汞', width: 70 },
+      { prop: 'cd', label: '镉', width: 70 },
+      { prop: 'cr6', label: '六价铬', width: 80 },
+      { prop: 'pb', label: '铅', width: 70 },
+      { prop: 'fe', label: '铁', width: 70 },
+      { prop: 'mn', label: '锰', width: 70 },
+      { prop: 'ni', label: '镍', width: 70 }
+    ],
+    historyChart: ['wt', 'ph', 'dox', 'codmn', 'codcr', 'bod5', 'nh3n', 'tp', 'tn']
+  },
+  TBA_SZY_RH: {
+    type: 'AWQMD',
+    columns: [
+      { prop: 'stcd', label: '站码', width: 100 },
+      { prop: 'stnm', label: '测站名称', width: 160 },
+      { prop: 'rvnm', label: '河流', width: 140 },
+      { prop: 'spt', label: '采样时间', width: 170, formatter: fmtDateTime },
+      { prop: 'wt', label: '水温(℃)', width: 90 },
+      { prop: 'ph', label: 'PH值', width: 80 },
+      { prop: 'cond', label: '电导率', width: 90 },
+      { prop: 'dox', label: '溶解氧', width: 90 },
+      { prop: 'codmn', label: '高锰酸盐指数', width: 120 },
+      { prop: 'codcr', label: '化学需氧量', width: 110 },
+      { prop: 'bod5', label: '五日生化需氧量', width: 130 },
+      { prop: 'nh3n', label: '氨氮', width: 90 },
+      { prop: 'tp', label: '总磷', width: 90 },
+      { prop: 'tn', label: '总氮', width: 90 },
+      { prop: 'turb', label: '浊度', width: 90 }
+    ],
+    historyChart: ['wt', 'ph', 'dox', 'codmn', 'codcr', 'bod5', 'nh3n', 'tp', 'tn']
+  },
+  TBA_SZ_1H2H: {
+    type: 'AWQMD',
+    columns: [
+      { prop: 'stcd', label: '站码', width: 100 },
+      { prop: 'stnm', label: '测站(口门)', width: 180 },
+      { prop: 'rvnm', label: '河流', width: 140 },
+      { prop: 'spt', label: '监测时间', width: 170, formatter: fmtDateTime },
+      { prop: 'wt', label: '水温(℃)', width: 90 },
+      { prop: 'ph', label: 'PH值', width: 80 },
+      { prop: 'cond', label: '电导率', width: 90 },
+      { prop: 'dox', label: '溶解氧', width: 90 },
+      { prop: 'codmn', label: '高锰酸盐指数', width: 120 },
+      { prop: 'nh3n', label: '氨氮', width: 90 },
+      { prop: 'tp', label: '总磷', width: 90 },
+      { prop: 'tn', label: '总氮', width: 90 },
+      { prop: 'turb', label: '浊度', width: 90 }
+    ],
+    historyChart: ['wt', 'ph', 'cond', 'dox', 'codmn', 'nh3n', 'tp', 'tn', 'turb']
+  },
+
+  // ========== 3. 蓝藻监测类 - LZDC / LZJC (AMR表为主) ==========
+  TBA_SYD_LZDC: {
+    type: 'AWQMD',
+    columns: [
+      { prop: 'stcd', label: '站码', width: 100 },
+      { prop: 'stnm', label: '测站名称', width: 160 },
+      { prop: 'spt', label: '采样时间', width: 170, formatter: fmtDateTime },
+      { prop: 'wt', label: '水温(℃)', width: 90 },
+      { prop: 'ph', label: 'PH值', width: 80 },
+      { prop: 'cond', label: '电导率', width: 90 },
+      { prop: 'dox', label: '溶解氧', width: 90 },
+      { prop: 'codmn', label: '高锰酸盐指数', width: 120 },
+      { prop: 'nh3n', label: '氨氮', width: 90 },
+      { prop: 'tp', label: '总磷', width: 90 },
+      { prop: 'tn', label: '总氮', width: 90 },
+      { prop: 'wndv', label: '风速', width: 80 },
+      { prop: 'wndpwr', label: '风力', width: 70 },
+      { prop: 'windir', label: '风向', width: 70 },
+      { prop: 'physp', label: '浮游植物种类', width: 140 },
+      { prop: 'phyd', label: '浮游植物密度', width: 130 },
+      { prop: 'cyncd', label: '蓝藻门细胞密度', width: 140 },
+      { prop: 'crycd', label: '隐藻门细胞密度', width: 140 },
+      { prop: 'pyrcd', label: '甲藻门细胞密度', width: 140 },
+      { prop: 'chrcd', label: '金藻门细胞密度', width: 140 },
+      { prop: 'xntcd', label: '黄藻门细胞密度', width: 140 },
+      { prop: 'bclcd', label: '硅藻门细胞密度', width: 140 },
+      { prop: 'eugcd', label: '裸藻门细胞密度', width: 140 },
+      { prop: 'chlcd', label: '绿藻门细胞密度', width: 140 },
+      { prop: 'phys', label: '浮游植物优势种', width: 160 },
+      { prop: 'phydd', label: '优势种密度', width: 140 },
+      { prop: 'phydi', label: '多样性指数', width: 120 }
+    ],
+    historyChart: ['wt', 'ph', 'dox', 'codmn', 'nh3n', 'tp', 'tn',
+      'phyd', 'cyncd', 'bclcd', 'chlcd', 'eugcd', 'crycd']
+  },
+  TBA_SZY_LZJC: {
+    type: 'AWQMD',
+    columns: [
+      { prop: 'stcd', label: '站码', width: 100 },
+      { prop: 'stnm', label: '测站名称', width: 160 },
+      { prop: 'spt', label: '采样时间', width: 170, formatter: fmtDateTime },
+      { prop: 'physp', label: '浮游植物种类', width: 140 },
+      { prop: 'phyd', label: '浮游植物密度', width: 130 },
+      { prop: 'phys', label: '主要优势种', width: 160 },
+      { prop: 'phydd', label: '优势种密度', width: 140 },
+      { prop: 'cyncd', label: '蓝藻门', width: 120 },
+      { prop: 'crycd', label: '隐藻门', width: 120 },
+      { prop: 'pyrcd', label: '甲藻门', width: 120 },
+      { prop: 'chrcd', label: '金藻门', width: 120 },
+      { prop: 'xntcd', label: '黄藻门', width: 120 },
+      { prop: 'bclcd', label: '硅藻门', width: 120 },
+      { prop: 'eugcd', label: '裸藻门', width: 120 },
+      { prop: 'chlcd', label: '绿藻门', width: 120 }
+    ],
+    historyChart: ['phyd', 'cyncd', 'crycd', 'pyrcd', 'chrcd', 'bclcd', 'eugcd', 'chlcd']
+  },
+
+  // ========== 4. 污染源类 - ST_WRY 表 ==========
+  JS_ST_WRY: {
+    type: 'WRY',
+    columns: [
+      { prop: 'stcd', label: '行政区编码', width: 120 },
+      { prop: 'stnm', label: '行政区名称', width: 200 },
+      { prop: 'year', label: '年份', width: 80 },
+      { prop: 'cod', label: 'COD排放量(吨)', width: 170 },
+      { prop: 'nh3', label: '氨氮排放量(吨)', width: 170 },
+      { prop: 'tn', label: '总氮排放量(吨)', width: 170 },
+      { prop: 'tp', label: '总磷排放量(吨)', width: 170 }
+    ],
+    historyChart: ['cod', 'nh3', 'tn', 'tp']
+  },
+  ZJ_ST_WRY: {
+    type: 'WRY',
+    columns: [
+      { prop: 'stcd', label: '行政区编码', width: 120 },
+      { prop: 'stnm', label: '行政区名称', width: 200 },
+      { prop: 'year', label: '年份', width: 80 },
+      { prop: 'cod', label: 'COD排放量(吨)', width: 170 },
+      { prop: 'nh3', label: '氨氮排放量(吨)', width: 170 },
+      { prop: 'tn', label: '总氮排放量(吨)', width: 170 },
+      { prop: 'tp', label: '总磷排放量(吨)', width: 170 }
+    ],
+    historyChart: ['cod', 'nh3', 'tn', 'tp']
+  },
+  SH_ST_WRY: {
+    type: 'WRY',
+    columns: [
+      { prop: 'stcd', label: '行政区编码', width: 120 },
+      { prop: 'stnm', label: '行政区名称', width: 200 },
+      { prop: 'year', label: '年份', width: 80 },
+      { prop: 'cod', label: 'COD排放量(吨)', width: 170 },
+      { prop: 'nh3', label: '氨氮排放量(吨)', width: 170 },
+      { prop: 'tn', label: '总氮排放量(吨)', width: 170 },
+      { prop: 'tp', label: '总磷排放量(吨)', width: 170 }
+    ],
+    historyChart: ['cod', 'nh3', 'tn', 'tp']
+  },
+
+  // ========== 5. 其他 / 降雨 ==========
+  TBA_JYL_FQ: {
+    type: 'DEFAULT',
+    columns: [
+      { prop: 'stnm', label: '测站名称', width: 160 },
+      { prop: 'spt', label: '时间', width: 170, formatter: fmtDateTime }
+    ],
+    historyChart: []
+  }
+}
+
+// ================ 计算属性 ================
+const currentConfig = computed(() => BIZ_COLUMN_CONFIG[selectedBiz.value] || {
+  type: 'DEFAULT',
+  columns: [
+    { prop: 'stcd', label: '站码', width: 100 },
+    { prop: 'stnm', label: '站名', width: 160 },
+    { prop: 'rvnm', label: '河流', width: 140 },
+    { prop: 'spt', label: '最新数据时间', width: 170, formatter: fmtDateTime }
+  ],
+  historyChart: []
+})
+
+const currentColumns = computed(() => currentConfig.value.columns || [])
+const historyColumns = computed(() => currentColumns.value)
 
-const isWryType = computed(() => {
-  return WRY_TYPES.includes(selectedBiz.value)
+const chartableMetrics = computed(() => {
+  const metrics = (currentConfig.value.historyChart || []).map(p => {
+    const col = currentColumns.value.find(c => c.prop === p)
+    return { prop: p, label: col ? col.label : p }
+  })
+  return metrics
 })
 
+function fmtDate(row, col, val) {
+  if (!val) return '-'
+  const d = new Date(val)
+  if (isNaN(d)) return String(val).slice(0, 10)
+  return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}`
+}
+function fmtYM(row, col, val) {
+  if (!val) return '-'
+  const d = new Date(val)
+  if (isNaN(d)) {
+    const s = String(val)
+    return s.length >= 7 ? s.slice(0, 7) : s
+  }
+  return `${d.getFullYear()}-${pad(d.getMonth()+1)}`
+}
+function fmtDateTime(row, col, val) {
+  if (!val) return '-'
+  const d = new Date(val)
+  if (isNaN(d)) return String(val)
+  return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`
+}
+function pad(n) { return n < 10 ? '0' + n : String(n) }
+
+// ================ 生命周期 ================
 onMounted(() => {
   loadUnits()
 })
@@ -138,35 +464,32 @@ function loadBizList(unitCode) {
     params: { bizUnit: unitCode }
   }).then(res => {
     const bizData = res.data || []
-    
-    Promise.all(bizData.map(biz => {
-      if (WRY_TYPES.includes(biz.bizCode)) {
-        return request({
-          url: '/gx/stWry/front/latest/' + biz.bizCode,
-          method: 'get'
-        }).then(wryRes => {
-          const data = wryRes.data || []
-          return { ...biz, stationCount: data.length }
-        })
-      } else {
-        return request({
-          url: '/gx/station/front/listByBizCode/' + biz.bizCode,
-          method: 'get'
-        }).then(stationRes => {
-          const stations = stationRes.data || []
-          return { ...biz, stationCount: stations.length }
-        })
+    if (bizData.length === 0) {
+      bizList.value = []
+      tableData.value = []
+      return
+    }
+    // 一次性批量查询所有业务域的站点数量
+    const bizCodes = bizData.map(b => b.bizCode)
+    request({
+      url: '/gx/station/front/countByBizCodes',
+      method: 'get',
+      params: { bizCodes: bizCodes.join(',') }
+    }).then(countRes => {
+      const countMap = countRes.data || {}
+      bizList.value = bizData.map(biz => ({
+        ...biz,
+        stationCount: countMap[biz.bizCode] || 0
+      }))
+      // 默认选中第一个业务域
+      if (bizList.value.length > 0) {
+        selectedBiz.value = bizList.value[0].bizCode
+        handleBizClick(selectedBiz.value)
       }
-    })).then(results => {
-      bizList.value = results
-      
+    }).catch(() => {
+      bizList.value = bizData.map(biz => ({ ...biz, stationCount: 0 }))
       if (bizList.value.length > 0) {
-        const defaultBiz = bizList.value.find(b => b.bizCode === selectedBiz.value)
-        if (defaultBiz) {
-          selectedBiz.value = defaultBiz.bizCode
-        } else {
-          selectedBiz.value = bizList.value[0].bizCode
-        }
+        selectedBiz.value = bizList.value[0].bizCode
         handleBizClick(selectedBiz.value)
       }
     })
@@ -175,108 +498,63 @@ function loadBizList(unitCode) {
 
 function handleBizClick(bizCode) {
   selectedBiz.value = bizCode
+  chartMetrics.value = []
   loadData()
 }
 
 function loadData() {
+  if (!selectedBiz.value) return
   loading.value = true
-  
-  if (isWryType.value) {
-    request({
-      url: '/gx/stWry/front/latest/' + selectedBiz.value,
-      method: 'get'
-    }).then(res => {
-      let data = res.data || []
-      
-      if (searchKeyword.value) {
-        const keyword = searchKeyword.value.toLowerCase()
-        data = data.filter(item => 
-          item.stnm.toLowerCase().includes(keyword)
-        )
-      }
-      
-      tableData.value = data
-      loading.value = false
-    }).catch(() => {
-      tableData.value = []
-      loading.value = false
-    })
-  } else {
-    request({
-      url: '/gx/station/front/listByBizCode/' + selectedBiz.value,
-      method: 'get'
-    }).then(res => {
-      const stationList = res.data || []
-      
-      let filteredStations = stationList
-      if (searchKeyword.value) {
-        const keyword = searchKeyword.value.toLowerCase()
-        filteredStations = filteredStations.filter(s => 
-          s.stnm.toLowerCase().includes(keyword) || 
-          s.stcd.toLowerCase().includes(keyword)
-        )
-      }
-      
-      Promise.all(filteredStations.map(station => {
-        return request({
-          url: '/gx/quality/front/list',
-          method: 'get',
-          params: { stcd: station.stcd, pageSize: 1 }
-        }).then(qualityRes => {
-          const qualityData = qualityRes.data || []
-          const latestQuality = qualityData.length > 0 ? qualityData[0] : {}
-          return {
-            ...station,
-            latestDataTime: latestQuality.uploadTime || '-',
-            wt: latestQuality.wt || '-',
-            ph: latestQuality.ph || '-',
-            do: latestQuality.do || '-',
-            nh3n: latestQuality.nh3n || '-',
-            tp: latestQuality.tp || '-',
-            tn: latestQuality.tn || '-',
-            cod: latestQuality.cod || '-',
-            bod: latestQuality.bod || '-'
-          }
-        })
-      })).then(results => {
-        tableData.value = results
-        loading.value = false
-      }).catch(() => {
-        tableData.value = filteredStations.map(station => ({
-          ...station,
-          latestDataTime: '-',
-          wt: '-',
-          ph: '-',
-          do: '-',
-          nh3n: '-',
-          tp: '-',
-          tn: '-',
-          cod: '-',
-          bod: '-'
-        }))
-        loading.value = false
+  request({
+    url: '/gx/dataCommon/front/latest/' + selectedBiz.value,
+    method: 'get'
+  }).then(res => {
+    let data = res.data || []
+    if (searchKeyword.value) {
+      const kw = searchKeyword.value.toLowerCase()
+      data = data.filter(item => {
+        const name = (item.stnm || '').toLowerCase()
+        const code = (item.stcd || '').toLowerCase()
+        const rvnm = (item.rvnm || '').toLowerCase()
+        return name.includes(kw) || code.includes(kw) || rvnm.includes(kw)
       })
-    }).catch(() => {
-      loading.value = false
-    })
-  }
+    }
+    tableData.value = data
+    loading.value = false
+  }).catch(err => {
+    console.error(err)
+    tableData.value = []
+    loading.value = false
+  })
 }
 
+// ================ 历史数据弹框 ================
 function handleRowClick(row) {
-  if (isWryType.value) {
-    historyDialogTitle.value = `${row.stnm} 历史数据`
-    loadHistoryData(row.stnm)
-    historyDialogVisible.value = true
+  const cfg = currentConfig.value
+  if (cfg.type === 'WRY') {
+    historyDialogTitle.value = `${row.stnm || ''} - 历史数据`
+    loadHistoryData(row.stcd, row.stnm)
+  } else {
+    const title = (row.stnm || row.STNM || '') + (row.stcd ? `(${row.stcd})` : '')
+    historyDialogTitle.value = `${title} - 历史数据`
+    loadHistoryData(row.stcd || row.STCD, row.stnm || row.STNM)
   }
+  historyDialogVisible.value = true
 }
 
-function loadHistoryData(stnm) {
+function loadHistoryData(stcd, stnm) {
   request({
-    url: '/gx/stWry/front/history',
+    url: '/gx/dataCommon/front/history',
     method: 'get',
-    params: { bizCode: selectedBiz.value, stnm: stnm }
+    params: {
+      bizCode: selectedBiz.value,
+      stcd: stcd || '',
+      stnm: stnm || ''
+    }
   }).then(res => {
     historyTableData.value = res.data || []
+    // 默认选择图表指标
+    chartMetrics.value = (currentConfig.value.historyChart || []).slice(0, 4)
     nextTick(() => {
       renderChart()
     })
@@ -285,81 +563,90 @@ function loadHistoryData(stnm) {
 
 function renderChart() {
   if (!chartRef.value) return
-  
   if (chartInstance) {
     chartInstance.dispose()
+    chartInstance = null
   }
-  
+  if (!chartMetrics.value || chartMetrics.value.length === 0) return
   chartInstance = echarts.init(chartRef.value)
-  
-  const sortedData = [...historyTableData.value].sort((a, b) => a.year - b.year)
-  const years = sortedData.map(item => item.year + '年')
-  const codData = sortedData.map(item => item.cod || 0)
-  const nh3Data = sortedData.map(item => item.nh3 || 0)
-  const tnData = sortedData.map(item => item.tn || 0)
-  const tpData = sortedData.map(item => item.tp || 0)
-  
+
+  // 按时间正序排列
+  const sorted = [...historyTableData.value].sort((a, b) => {
+    const ta = getTimeVal(a.spt || a.SPT || a.year)
+    const tb = getTimeVal(b.spt || b.SPT || b.year)
+    return ta - tb
+  })
+  const xData = sorted.map(item => {
+    const v = item.spt || item.SPT || item.year
+    if (typeof v === 'number') return v + '年'
+    const d = new Date(v)
+    if (isNaN(d)) return String(v).slice(0, 10)
+    return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}`
+  })
+  function getTimeVal(v) {
+    if (typeof v === 'number') return v
+    const d = new Date(v)
+    return isNaN(d) ? 0 : d.getTime()
+  }
+
+  const palette = ['#1c97e7', '#ffcc4d', '#93d347', '#ff8562', '#8d6cc7',
+                   '#39c5c9', '#f46687', '#60a5fa', '#f59e0b', '#10b981',
+                   '#ef4444', '#8b5cf6', '#06b6d4', '#f97316', '#84cc16']
+  const series = chartMetrics.value.map((mp, idx) => {
+    const col = chartableMetrics.value.find(m => m.prop === mp)
+    return {
+      name: col ? col.label : mp,
+      type: 'line',
+      smooth: true,
+      symbol: 'circle',
+      symbolSize: 5,
+      itemStyle: { color: palette[idx % palette.length] },
+      data: sorted.map(item => numOrNull(item[mp] || item[mp?.toUpperCase?.()] || item[(mp || '').toLowerCase?.()]))
+    }
+  })
+  function numOrNull(v) {
+    if (v === null || v === undefined || v === '') return null
+    const n = Number(v)
+    return isNaN(n) ? null : n
+  }
+
   const option = {
     tooltip: {
       trigger: 'axis',
-      axisPointer: {
-        type: 'shadow'
-      }
+      axisPointer: { type: 'cross' }
     },
     legend: {
-      data: ['COD', '氨氮', '总氮', '总磷'],
-      bottom: 10
+      bottom: 10,
+      data: series.map(s => s.name)
     },
     grid: {
-      left: '3%',
-      right: '4%',
-      bottom: '15%',
-      top: '10%',
-      containLabel: true
+      left: '3%', right: '4%', bottom: '18%', top: '10%', containLabel: true
     },
     xAxis: {
       type: 'category',
-      data: years,
-      axisLabel: {
-        rotate: 45
-      }
+      data: xData,
+      axisLabel: { rotate: 40, fontSize: 11 }
     },
     yAxis: {
-      type: 'value'
+      type: 'value',
+      scale: true
     },
-    series: [
-      {
-        name: 'COD',
-        type: 'bar',
-        data: codData,
-        itemStyle: { color: '#1c97e7' }
-      },
-      {
-        name: '氨氮',
-        type: 'bar',
-        data: nh3Data,
-        itemStyle: { color: '#ffcc4d' }
-      },
-      {
-        name: '总氮',
-        type: 'bar',
-        data: tnData,
-        itemStyle: { color: '#93d347' }
-      },
-      {
-        name: '总磷',
-        type: 'bar',
-        data: tpData,
-        itemStyle: { color: '#ff8562' }
-      }
-    ]
+    dataZoom: [
+      { type: 'inside', start: 0, end: 100 },
+      { type: 'slider', start: 0, end: 100, bottom: 50, height: 18 }
+    ],
+    series
   }
-  
   chartInstance.setOption(option)
 }
 
+// 窗口变化 resize
+window.addEventListener('resize', () => chartInstance && chartInstance.resize())
+
 watch(historyDialogVisible, (val) => {
-  if (!val && chartInstance) {
+  if (val) {
+    nextTick(() => chartInstance && chartInstance.resize())
+  } else if (chartInstance) {
     chartInstance.dispose()
     chartInstance = null
   }
@@ -371,15 +658,19 @@ watch(historyDialogVisible, (val) => {
   height: 100%;
   display: flex;
   flex-direction: column;
+  padding: 16px;
+  box-sizing: border-box;
 }
 
 .page-content {
   flex: 1;
   background: #fff;
   border-radius: 12px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
   display: flex;
   flex-direction: column;
   overflow: hidden;
+  min-height: 0;
 }
 
 .search-bar {
@@ -388,21 +679,19 @@ watch(historyDialogVisible, (val) => {
   display: flex;
   align-items: center;
   justify-content: space-between;
+  flex-wrap: wrap;
+  gap: 12px;
 }
 
-.search-left {
-  flex-shrink: 0;
-}
-
-.unit-select {
-  width: 220px;
-}
+.search-left { flex-shrink: 0; }
+.unit-select { width: 220px; }
 
 .search-center {
   flex: 1;
   display: flex;
   justify-content: center;
   padding: 0 20px;
+  min-width: 0;
 }
 
 .biz-buttons {
@@ -424,42 +713,34 @@ watch(historyDialogVisible, (val) => {
   align-items: center;
   gap: 6px;
 }
-
 .biz-buttons :deep(.el-button:hover) {
   background: #ecf5ff;
   border-color: #b3d8ff;
   color: #1c97e7;
 }
-
 .biz-buttons :deep(.el-button.active) {
   background: #1c97e7;
   color: #fff;
   border-color: #1c97e7;
 }
-
 .station-count {
   font-size: 12px;
   background: rgba(255, 255, 255, 0.2);
   padding: 2px 6px;
   border-radius: 10px;
 }
-
 .biz-buttons :deep(.el-button.active) .station-count {
   background: rgba(255, 255, 255, 0.3);
 }
 
-.search-right {
-  flex-shrink: 0;
-}
+.search-right { flex-shrink: 0; }
 
 .table-container {
   flex: 1;
-  overflow-y: auto;
-  padding: 20px;
-}
-
-.table-container :deep(.el-table) {
-  width: 100%;
+  overflow: hidden;
+  padding: 12px 16px 16px;
+  position: relative;
+  min-height: 0;
 }
 
 .table-container :deep(.el-table tr:hover) {
@@ -472,38 +753,50 @@ watch(historyDialogVisible, (val) => {
   flex-direction: column;
   align-items: center;
   justify-content: center;
-  height: 300px;
+  height: 100%;
+  min-height: 300px;
   color: #909399;
+  position: absolute;
+  top: 0; left: 0; right: 0; bottom: 0;
 }
-
 .empty-icon {
   font-size: 64px;
   margin-bottom: 16px;
   opacity: 0.5;
 }
+.empty-state p { font-size: 16px; margin: 0; }
 
-.empty-state p {
-  font-size: 16px;
-  margin: 0;
-}
-
+/* ========= 弹框布局 ========= */
 .history-dialog-content {
   display: flex;
   gap: 20px;
-  height: 500px;
+  height: 600px;
+  padding: 4px;
 }
-
 .history-table {
   flex: 1;
-  overflow-y: auto;
+  overflow: hidden;
+  border: 1px solid #ebeef5;
+  border-radius: 6px;
+  min-width: 0;
 }
-
 .history-chart {
   flex: 1;
+  display: flex;
+  flex-direction: column;
+  min-width: 0;
+}
+.chart-title {
+  padding: 10px 4px 12px;
+  font-size: 15px;
+  font-weight: 600;
+  color: #303133;
+  border-bottom: 1px dashed #e4e7ed;
+  margin-bottom: 8px;
 }
-
 .chart-container {
+  flex: 1;
   width: 100%;
-  height: 100%;
+  min-height: 480px;
 }
 </style>