import sys
import sqlite3
import serial
import warnings
warnings.filterwarnings("ignore")  # 彻底关闭所有警告
from datetime import datetime
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import QFont, QPainter, QColor, QPen, QIcon
from PyQt5.QtChart import QChart, QChartView, QLineSeries, QValueAxis, QDateTimeAxis
import numpy as np
import pandas as pd
import matplotlib
from PyQt5.QtCore import Qt
matplotlib.use('Qt5Agg')
import matplotlib.pyplot as plt
from matplotlib.lines import Line2D
from PyQt5.QtWidgets import QFileDialog, QTextEdit
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as MplFigureCanvas
from matplotlib.figure import Figure
# 新增：插值依赖（水位-库容曲线平滑）
from scipy.interpolate import pchip_interpolate
from PyQt5.QtCore import QSettings
import base64
try:
    from PyQt5 import sip
except ImportError:
    import sip

class FigureCanvas(MplFigureCanvas):
    def draw_idle(self, *args, **kwargs):
        try:
            return super().draw_idle(*args, **kwargs)
        except RuntimeError:
            return None

    def _draw_idle(self):
        try:
            with self._idle_draw_cntx():
                if not getattr(self, "_draw_pending", False):
                    return
                self._draw_pending = False
                if self.width() <= 0 or self.height() <= 0:
                    return
                self.draw()
        except RuntimeError:
            try:
                self._draw_pending = False
            except Exception:
                pass

    def draw(self, *args, **kwargs):
        try:
            return super().draw(*args, **kwargs)
        except RuntimeError:
            try:
                self._draw_pending = False
            except Exception:
                pass
            return None

    def paintEvent(self, event):
        try:
            return super().paintEvent(event)
        except RuntimeError:
            return None

# ===================== 登录界面 =====================
class LoginWindow(QWidget):
    login_success = pyqtSignal()  # 登录成功信号

    def __init__(self):
        super().__init__()
        self.setWindowTitle("大坝智慧监测系统 - 登录")
        self.setFixedSize(420, 320)
        self.setStyleSheet("background-color: #F5F7FA;")
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()
        layout.setAlignment(Qt.AlignCenter)
        layout.setSpacing(20)
        layout.setContentsMargins(50, 50, 50, 50)

        # 标题
        title = QLabel("大坝智慧监测系统")
        title.setAlignment(Qt.AlignCenter)
        title.setStyleSheet("font-size: 22px; font-weight: bold; color: #1D2129;")
        layout.addWidget(title)

        # 账号输入
        self.username = QLineEdit()
        self.username.setPlaceholderText("请输入账号")
        self.username.setStyleSheet("""
            QLineEdit {
                border: 1px solid #E5E6EB;
                border-radius: 6px;
                padding: 12px;
                font-size: 14px;
                background-color: white;
                min-height: 40px;
            }
            QLineEdit:focus {
                border: 1px solid #2A7FFF;
            }
        """)
        layout.addWidget(self.username)

        # 密码输入
        self.password = QLineEdit()
        self.password.setPlaceholderText("请输入密码")
        self.password.setEchoMode(QLineEdit.Password)
        self.password.setStyleSheet("""
            QLineEdit {
                border: 1px solid #E5E6EB;
                border-radius: 6px;
                padding: 12px;
                font-size: 14px;
                background-color: white;
                min-height: 40px;
            }
            QLineEdit:focus {
                border: 1px solid #2A7FFF;
            }
        """)
        layout.addWidget(self.password)

        # 新增：记住密码复选框
        self.check_remember = QCheckBox("记住密码")
        self.check_remember.setStyleSheet("""
            QCheckBox {
                font-size: 13px;
                color: #4E5969;
                spacing: 5px;
            }
        """)
        layout.addWidget(self.check_remember)

        # 登录按钮
        self.btn_login = QPushButton("登 录")
        self.btn_login.clicked.connect(self.check_login)
        self.btn_login.setStyleSheet("""
            QPushButton {
                background-color: #2A7FFF;
                color: white;
                border-radius: 6px;
                padding: 12px;
                font-size: 15px;
                font-weight: bold;
                border: none;
                min-height: 44px;
            }
            QPushButton:hover {
                background-color: #4096ff;
            }
            QPushButton:pressed {
                background-color: #1b6aff;
            }
        """)
        layout.addWidget(self.btn_login)

        self.setLayout(layout)

        # ========== 核心：读取并填充上次保存的账号密码（默认 admin/123456） ==========
        self.settings = QSettings("DamMonitor", "SmartDamSystem")
        # 读取保存的账号（如果没有则默认 admin）
        saved_user = self.settings.value("account", "admin")
        # 读取保存的密码（如果没有则默认 123456）
        saved_pwd_enc = self.settings.value("password", "")
        saved_pwd = self._decrypt_pwd(saved_pwd_enc) if saved_pwd_enc else "123456"
        # 读取是否勾选了记住密码
        is_remember = self.settings.value("remember", True, type=bool)

        # 填充到输入框
        self.username.setText(saved_user)
        self.password.setText(saved_pwd)
        self.check_remember.setChecked(is_remember)

    # ========== 辅助：简单加密解密（避免明文存密码） ==========
    def _encrypt_pwd(self, pwd):
        return base64.b64encode(pwd.encode('utf-8')).decode('utf-8')

    def _decrypt_pwd(self, enc_pwd):
        try:
            return base64.b64decode(enc_pwd.encode('utf-8')).decode('utf-8')
        except:
            return "123456"

    def check_login(self):
        user = self.username.text().strip()
        pwd = self.password.text().strip()

        # 默认账号密码验证
        if user == "admin" and pwd == "123456":
            # ========== 核心：登录成功后保存账号密码 ==========
            if self.check_remember.isChecked():
                self.settings.setValue("account", user)
                self.settings.setValue("password", self._encrypt_pwd(pwd))
                self.settings.setValue("remember", True)
            else:
                # 不记住密码时，只保留账号，清空密码
                self.settings.setValue("account", user)
                self.settings.remove("password")
                self.settings.setValue("remember", False)

            self.login_success.emit()
            self.close()
        else:
            QMessageBox.warning(self, "登录失败", "账号或密码错误！")


# ===================== 全局配置 =====================
DB_PATH = r"D:\数据库\数据库测试\test.db"
COM_PORT = "COM3"
BAUDRATE = 9600
SERIAL_CMD = bytes([0x04, 0x03, 0x00, 0x04, 0x00, 0x01, 0xC5, 0x9E])
ALL_TABLES = ["渗压计", "土压力计", "水位计", "沉降仪", "测斜仪", "GNSS测点", "气象站"]
# 钉钉风格子标签页样式（提前定义，避免作用域问题）
TAB_STYLE = """
    QTabWidget::pane {
        border: none;
        background-color: #F5F7FA;
    }
    QTabBar::tab {
        font-family: "Microsoft YaHei";
        padding: 12px 24px;
        font-size: 14px;
        background-color: transparent;
        border: none;
        color: #4E5969;
        min-width: 86px;
        min-height: 44px;
        margin-right: 6px;
    }
    QTabBar::tab:selected {
        color: #2A7FFF;
        font-weight: bold;
        border-bottom: 2px solid #2A7FFF;
        background-color: transparent;
    }
    QTabBar::tab:hover {
        color: #2A7FFF;
        background-color: #E8F3FF;
        border-radius: 6px 6px 0 0;
    }
"""
# ====================================================

# ===================== 0. 系统首页（大坝布置图 PDF 查看） =====================
import fitz
import os
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import QPixmap, QImage, QPainter

class PDFView(QGraphicsView):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setRenderHint(QPainter.Antialiasing)
        self.setDragMode(QGraphicsView.ScrollHandDrag)  # 左键拖动
        self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
        self.setResizeAnchor(QGraphicsView.AnchorUnderMouse)

        # 关闭自带的滚动条，避免冲突
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

        self.scene = QGraphicsScene()
        self.setScene(self.scene)
        self.zoom_factor = 1.0

    # 核心修复：滚轮事件 → 缩放，不是滚动
    def wheelEvent(self, event):
        # 滚轮向上 = 放大
        if event.angleDelta().y() > 0:
            self.scale(1.2, 1.2)
        # 滚轮向下 = 缩小
        else:
            self.scale(0.8, 0.8)

    def load_pdf(self, path):
        self.scene.clear()
        try:
            doc = fitz.open(path)
            page = doc.load_page(0)
            pix = page.get_pixmap(matrix=fitz.Matrix(4, 4))  # 更清晰
            img = QImage(pix.samples, pix.width, pix.height, pix.stride, QImage.Format_RGB888)
            pixmap = QPixmap.fromImage(img)
            self.scene.addPixmap(pixmap)
            self.resetTransform()
            self.scale(0.5, 0.5)
        except Exception as e:
            self.scene.addText(f"加载失败：{str(e)}")

class HomePage(QWidget):
    def __init__(self):
        super().__init__()
        self.setStyleSheet("background-color: #F5F7FA;")
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout(self)
        layout.setContentsMargins(24, 20, 24, 20)
        layout.setSpacing(16)

        top_card = QWidget()
        top_card.setStyleSheet("""
            QWidget { background-color: white; border-radius: 8px; border: 1px solid #E5E6EB; }
        """)
        top_layout = QVBoxLayout(top_card)
        top_layout.setContentsMargins(20, 18, 20, 18)
        top_layout.setSpacing(14)

        title = QLabel("🏠 大坝监测仪器布置图")
        title.setFont(QFont("Microsoft YaHei", 16, QFont.Bold))
        title.setStyleSheet("color: #1D2129;")
        top_layout.addWidget(title)

        self.tab_bar = QTabWidget()
        self.tab_bar.setStyleSheet("""
            QTabWidget::pane { border: none; background-color: #F7F8FA; border-radius:6px; }
            QTabBar::tab { font-family: "Microsoft YaHei"; padding: 10px 22px; font-size:14px; min-width:128px; min-height:36px; background-color: #E8F3FF; color:#2A7FFF; border-radius:6px; margin-right:8px; }
            QTabBar::tab:selected { background-color: #2A7FFF; color:white; font-weight:bold; }
        """)
        self.tab_bar.tabBar().setElideMode(Qt.ElideNone)
        self.tab_bar.tabBar().setUsesScrollButtons(True)

        desktop = os.path.join(os.path.expanduser("~"), "Desktop")
        base_path = os.path.join(desktop, "毕业设计", "打印PDF或png")

        self.pdf_list = {
            "大坝平面布置图": os.path.join(base_path, "平面图-Model.pdf"),
            "0+084横剖面图": os.path.join(base_path, "0+084.pdf"),
            "0+115横剖面图": os.path.join(base_path, "0+115.pdf"),
            "0+148横剖面图": os.path.join(base_path, "0+148.pdf")
        }

        for name, path in self.pdf_list.items():
            view = PDFView()
            view.load_pdf(path)
            self.tab_bar.addTab(view, name)

        top_layout.addWidget(self.tab_bar)
        layout.addWidget(top_card)

        tip_card = QWidget()
        tip_card.setStyleSheet("background-color: #E8F3FF; border-radius:8px;")
        tip_layout = QHBoxLayout(tip_card)
        tip_layout.setContentsMargins(16,12,16,12)
        tip = QLabel("📌 操作说明：滚轮缩放图纸，按住鼠标左键拖动查看全图")
        tip.setFont(QFont("Microsoft YaHei", 12))
        tip.setStyleSheet("color:#1D2129;")
        tip_layout.addWidget(tip)
        layout.addWidget(tip_card)

# ===================== 1. 数据录入（数据存储）子页面 =====================
class InstrumentEditPage(QWidget):
    def __init__(self, table_name):
        super().__init__()
        self.table_name = table_name
        self.setStyleSheet("background-color: #F5F7FA;")
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout(self)
        layout.setContentsMargins(24, 20, 24, 20)
        layout.setSpacing(16)

        # 输入卡片
        card = QWidget()
        card.setStyleSheet("""
            QWidget {
                background-color: white;
                border-radius: 8px;
                border: 1px solid #E5E6EB;
            }
        """)
        card_layout = QVBoxLayout(card)
        card_layout.setContentsMargins(20, 18, 20, 18)
        card_layout.setSpacing(14)

        title = QLabel(f"{self.table_name} 数据录入")
        title.setFont(QFont("Microsoft YaHei", 16, QFont.Bold))
        title.setStyleSheet("color: #1D2129;")
        card_layout.addWidget(title)

        # 仪器选择
        self.cb_dev = QComboBox()
        self.cb_dev.addItem("请选择仪器编号")
        self.cb_dev.setStyleSheet("""
            QComboBox {
                border: 1px solid #E5E6EB;
                border-radius: 6px;
                padding: 10px 12px;
                font-size: 14px;
                color: #4E5969;
                background-color: white;
                min-height: 40px;
            }
            QComboBox::drop-down {
                border: none;
                width: 20px;
            }
            QComboBox QAbstractItemView {
                border: 1px solid #E5E6EB;
                border-radius: 6px;
                background-color: white;
                selection-background-color: #E8F3FF;
                selection-color: #2A7FFF;
            }
        """)
        self.load_instrument_list()
        card_layout.addWidget(self.cb_dev)

        # 日期输入
        self.edit_date = QLineEdit()
        self.edit_date.setPlaceholderText("请输入日期，例如：2026年3月2日")
        self.edit_date.setStyleSheet("""
            QLineEdit {
                border: 1px solid #E5E6EB;
                border-radius: 6px;
                padding: 10px 12px;
                font-size: 14px;
                color: #4E5969;
                min-height: 40px;
                background-color: white;
            }
            QLineEdit:focus {
                border: 1px solid #2A7FFF;
            }
        """)
        card_layout.addWidget(self.edit_date)

        # 手动数据输入（新增）
        self.edit_val = QLineEdit()
        self.edit_val.setPlaceholderText("无串口时可手动输入监测数据（数字）")
        self.edit_val.setStyleSheet("""
            QLineEdit {
                border: 1px solid #E5E6EB;
                border-radius: 6px;
                padding: 10px 12px;
                font-size: 14px;
                color: #4E5969;
                min-height: 40px;
                background-color: #f9fafb;
            }
            QLineEdit:focus {
                border: 1px solid #2A7FFF;
            }
        """)
        card_layout.addWidget(self.edit_val)

        # 按钮
        self.btn_read = QPushButton(f"读取并保存 {self.table_name} 数据")
        self.btn_read.clicked.connect(self.read_and_save)
        self.btn_read.setStyleSheet("""
            QPushButton {
                background-color: #2A7FFF;
                color: white;
                border-radius: 6px;
                padding: 11px;
                font-size: 14px;
                font-weight: bold;
                border: none;
                min-height: 44px;
            }
            QPushButton:hover {
                background-color: #4096ff;
            }
            QPushButton:pressed {
                background-color: #1b6aff;
            }
        """)
        card_layout.addWidget(self.btn_read)
        layout.addWidget(card)

        # 状态（修正self.前缀）
        self.status_label = QLabel("就绪")
        self.status_label.setFont(QFont("Microsoft YaHei", 13))
        self.status_label.setStyleSheet("color: #86909C; padding: 4px 0;")
        layout.addWidget(self.status_label)

        # 表格卡片
        table_card = QWidget()
        table_card.setStyleSheet("""
            QWidget {
                background-color: white;
                border-radius: 8px;
                border: none;
            }
        """)
        table_layout = QVBoxLayout(table_card)
        table_layout.setContentsMargins(14, 14, 14, 14)

        self.table = QTableWidget()
        self.table.setStyleSheet("""
            QTableWidget {
                gridline-color: #F2F3F5;
                font-size: 13px;
                border: none;
                background-color: white;
                alternate-background-color: #F7F8FA;
            }
            QHeaderView::section {
                background-color: #F7F8FA;
                padding: 10px;
                border: none;
                font-weight: bold;
                color: #4E5969;
                font-size: 13px;
                border-bottom: 1px solid #F2F3F5;
            }
            QTableWidget::item {
                padding: 8px;
                border: none;
            }
            QTableWidget::item:selected {
                background-color: #E8F3FF;
                color: #2A7FFF;
            }
        """)
        self.table.setAlternatingRowColors(True)
        table_layout.addWidget(self.table)
        layout.addWidget(table_card)

        self.load_table()

    def load_instrument_list(self):
        try:
            conn = sqlite3.connect(DB_PATH)
            cur = conn.cursor()
            cur.execute(f"SELECT 仪器编号 FROM `{self.table_name}`")
            rows = cur.fetchall()
            conn.close()
            for row in rows:
                self.cb_dev.addItem(row[0])
        except:
            pass

    def read_serial(self):
        try:
            ser = serial.Serial(COM_PORT, BAUDRATE, timeout=3)
            ser.write(SERIAL_CMD)
            data = ser.read(7)
            ser.close()
            if len(data) < 7:
                return None
            return int(data.hex()[6:10], 16) / 10
        except:
            return None

    def read_and_save(self):
        dev_id = self.cb_dev.currentText().strip()
        date_col = self.edit_date.text().strip()
        manual_val = self.edit_val.text().strip()

        if dev_id == "请选择仪器编号" or not dev_id:
            QMessageBox.warning(self, "提示", "请选择仪器编号")
            return
        if not date_col:
            QMessageBox.warning(self, "提示", "请输入日期")
            return

        # 优先串口，失败则使用手动输入
        val = self.read_serial()
        if val is None:
            try:
                val = float(manual_val)
            except:
                QMessageBox.critical(self, "失败", "串口读取失败，请手动输入有效数字")
                return

        try:
            conn = sqlite3.connect(DB_PATH)
            cur = conn.cursor()
            cur.execute(f"ALTER TABLE `{self.table_name}` ADD COLUMN `{date_col}` REAL")
            conn.commit()
            conn.close()
        except:
            pass

        try:
            conn = sqlite3.connect(DB_PATH)
            cur = conn.cursor()
            cur.execute(f"UPDATE `{self.table_name}` SET `{date_col}`=? WHERE 仪器编号=?", (val, dev_id))
            conn.commit()
            conn.close()
            self.status_label.setText(f"✅ 已保存：{dev_id} │ {date_col} │ {val}")
            self.load_table()
        except:
            QMessageBox.critical(self, "错误", "保存失败")

    def load_table(self):
        try:
            conn = sqlite3.connect(DB_PATH)
            cur = conn.cursor()
            cur.execute(f"SELECT * FROM `{self.table_name}`")
            rows = cur.fetchall()
            headers = [d[0] for d in cur.description]
            conn.close()

            self.table.setRowCount(len(rows))
            self.table.setColumnCount(len(headers))
            self.table.setHorizontalHeaderLabels(headers)

            for r, row in enumerate(rows):
                for c, item in enumerate(row):
                    self.table.setItem(r, c, QTableWidgetItem(str(item)))
        except:
            pass

# ===================== 2. 数据查询子页面 =====================
class InstrumentQueryPage(QWidget):
    def __init__(self, table_name):
        super().__init__()
        self.table_name = table_name
        self.setStyleSheet("background-color: #F5F7FA;")
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout(self)
        layout.setContentsMargins(24, 20, 24, 20)
        layout.setSpacing(16)

        card = QWidget()
        card.setStyleSheet("""
            QWidget {
                background-color: white;
                border-radius: 8px;
                border: 1px solid #E5E6EB;
            }
        """)
        card_layout = QVBoxLayout(card)
        card_layout.setContentsMargins(20, 18, 20, 18)
        card_layout.setSpacing(14)

        title = QLabel(f"{self.table_name} 数据查询")
        title.setFont(QFont("Microsoft YaHei", 16, QFont.Bold))
        title.setStyleSheet("color: #1D2129;")
        card_layout.addWidget(title)

        self.cb_dev = QComboBox()
        self.cb_dev.addItem("请选择仪器编号")
        self.cb_dev.setStyleSheet("""
            QComboBox {
                border: 1px solid #E5E6EB;
                border-radius: 6px;
                padding: 10px 12px;
                font-size: 14px;
                color: #4E5969;
                background-color: white;
                min-height: 40px;
            }
            QComboBox::drop-down { border: none; width: 20px; }
            QComboBox QAbstractItemView {
                border: 1px solid #E5E6EB;
                border-radius: 6px;
                background-color: white;
                selection-background-color: #E8F3FF;
                selection-color: #2A7FFF;
            }
        """)
        self.load_instruments()
        card_layout.addWidget(self.cb_dev)

        self.edit_date = QLineEdit()
        self.edit_date.setPlaceholderText("请输入日期，例如：2026年3月2日")
        self.edit_date.setStyleSheet("""
            QLineEdit {
                border: 1px solid #E5E6EB;
                border-radius: 6px;
                padding: 10px 12px;
                font-size: 14px;
                color: #4E5969;
                min-height: 40px;
                background-color: white;
            }
            QLineEdit:focus { border: 1px solid #2A7FFF; }
        """)
        card_layout.addWidget(self.edit_date)

        btn_query = QPushButton(f"查询 {self.table_name} 数据")
        btn_query.clicked.connect(self.do_query)
        btn_query.setStyleSheet("""
            QPushButton {
                background-color: #2A7FFF;
                color: white;
                border-radius: 6px;
                padding: 11px;
                font-size: 14px;
                font-weight: bold;
                border: none;
                min-height: 44px;
            }
            QPushButton:hover { background-color: #4096ff; }
            QPushButton:pressed { background-color: #1b6aff; }
        """)
        card_layout.addWidget(btn_query)

        self.result_label = QLabel("查询结果：")
        self.result_label.setFont(QFont("Microsoft YaHei", 14))
        self.result_label.setStyleSheet("color: #4E5969; padding: 8px 0;")
        card_layout.addWidget(self.result_label)

        layout.addWidget(card)

    def load_instruments(self):
        try:
            conn = sqlite3.connect(DB_PATH)
            cur = conn.cursor()
            cur.execute(f"SELECT 仪器编号 FROM `{self.table_name}`")
            rows = cur.fetchall()
            conn.close()
            for r in rows:
                self.cb_dev.addItem(r[0])
        except:
            pass

    def do_query(self):
        dev_id = self.cb_dev.currentText().strip()
        date_col = self.edit_date.text().strip()

        if dev_id == "请选择仪器编号" or not date_col:
            QMessageBox.warning(self, "提示", "请选择编号并输入日期")
            return

        try:
            conn = sqlite3.connect(DB_PATH)
            cur = conn.cursor()
            cur.execute(f"SELECT `{date_col}` FROM `{self.table_name}` WHERE 仪器编号=?", (dev_id,))
            res = cur.fetchone()
            conn.close()

            if res and res[0] is not None:
                self.result_label.setText(f"✅ 结果：{dev_id} │ {date_col} = {res[0]}")
            else:
                self.result_label.setText("❌ 无数据或日期列不存在")
        except Exception as e:
            self.result_label.setText("❌ 查询失败：" + str(e))

# ===================== 3. 仪器统计页面 =====================
class InstrumentStatPage(QWidget):
    def __init__(self):
        super().__init__()
        self.setStyleSheet("background-color: #F5F7FA;")
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout(self)
        layout.setContentsMargins(24, 20, 24, 20)
        layout.setSpacing(16)

        # 统计概览
        stat_card = QWidget()
        stat_card.setStyleSheet("""
            QWidget {
                background-color: white;
                border-radius: 8px;
                border: 1px solid #E5E6EB;
            }
        """)
        stat_layout = QGridLayout(stat_card)
        stat_layout.setContentsMargins(20, 18, 20, 18)
        stat_layout.setSpacing(16)

        self.stat_labels = {}
        row, col = 0, 0
        for table in ALL_TABLES:
            card = QWidget()
            card.setStyleSheet("""
                QWidget {
                    background-color: #F7F8FA;
                    border-radius: 8px;
                    border: 1px solid #E5E6EB;
                }
            """)
            card_layout = QVBoxLayout(card)
            card_layout.setContentsMargins(16, 14, 16, 14)
            title = QLabel(table)
            title.setFont(QFont("Microsoft YaHei", 13, QFont.Bold))
            title.setStyleSheet("color: #2A7FFF;")
            count_label = QLabel("仪器数量：加载中...")
            count_label.setFont(QFont("Microsoft YaHei", 12))
            count_label.setStyleSheet("color: #4E5969;")
            card_layout.addWidget(title)
            card_layout.addWidget(count_label)
            self.stat_labels[table] = count_label
            stat_layout.addWidget(card, row, col)
            col += 1
            if col == 4:
                col = 0
                row += 1

        layout.addWidget(stat_card)

        # 全表展示
        table_card = QWidget()
        table_card.setStyleSheet("""
            QWidget {
                background-color: white;
                border-radius: 8px;
                border: 1px solid #E5E6EB;
            }
        """)
        table_layout = QVBoxLayout(table_card)
        table_layout.setContentsMargins(14, 14, 14, 14)

        self.table_tabs = QTabWidget()
        self.table_tabs.setStyleSheet("""
            QTabWidget::pane { border: none; background-color: white; }
            QTabBar::tab {
                font-family: "Microsoft YaHei";
                padding: 10px 22px;
                font-size: 14px;
                background-color: #F7F8FA;
                border: 1px solid #E5E6EB;
                border-bottom: none;
                border-top-left-radius: 6px;
                border-top-right-radius: 6px;
                margin-right: 4px;
                color: #4E5969;
                min-width: 86px;
                min-height: 42px;
            }
            QTabBar::tab:selected {
                background-color: white;
                color: #2A7FFF;
                font-weight: bold;
                border-bottom: 2px solid #2A7FFF;
            }
            QTabBar::tab:hover {
                background-color: #E8F3FF;
            }
        """)
        self.table_tabs.tabBar().setElideMode(Qt.ElideNone)
        self.table_tabs.tabBar().setUsesScrollButtons(True)
        table_layout.addWidget(self.table_tabs)
        layout.addWidget(table_card)

        self.load_stats()
        self.load_all_tables()

    def load_stats(self):
        for table in ALL_TABLES:
            try:
                conn = sqlite3.connect(DB_PATH)
                cur = conn.cursor()
                cur.execute(f"SELECT COUNT(*) FROM `{table}`")
                count = cur.fetchone()[0]
                conn.close()
                self.stat_labels[table].setText(f"仪器数量：{count} 台")
            except:
                self.stat_labels[table].setText("仪器数量：加载失败")

    def load_all_tables(self):
        for table in ALL_TABLES:
            table_widget = QTableWidget()
            table_widget.setStyleSheet("""
                QTableWidget {
                    gridline-color: #F2F3F5;
                    font-size: 12px;
                    border: none;
                    background-color: white;
                    alternate-background-color: #F7F8FA;
                }
                QHeaderView::section {
                    background-color: #F7F8FA;
                    padding: 8px;
                    border: none;
                    font-weight: bold;
                    color: #4E5969;
                    border-bottom: 1px solid #F2F3F5;
                }
                QTableWidget::item {
                    padding: 6px;
                    border: none;
                }
            """)
            table_widget.setAlternatingRowColors(True)
            try:
                conn = sqlite3.connect(DB_PATH)
                cur = conn.cursor()
                cur.execute(f"SELECT * FROM `{table}`")
                rows = cur.fetchall()
                headers = [d[0] for d in cur.description]
                conn.close()

                table_widget.setRowCount(len(rows))
                table_widget.setColumnCount(len(headers))
                table_widget.setHorizontalHeaderLabels(headers)
                for r, row in enumerate(rows):
                    for c, item in enumerate(row):
                        table_widget.setItem(r, c, QTableWidgetItem(str(item)))
            except:
                pass
            self.table_tabs.addTab(table_widget, table)

# ===================== 4. 数据分析页面（含折线图 + 浸润线图） =====================
class DataAnalysisPage(QWidget):
    def __init__(self):
        super().__init__()
        self.setStyleSheet("background-color: #F5F7FA;")
        self.mpl_connections = []
        self.mpl_canvases = []
        self._retired_mpl_canvases = []
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout(self)
        layout.setContentsMargins(24, 20, 24, 20)
        layout.setSpacing(16)

        # 子页面切换标签
        self.tab_widget = QTabWidget()
        self.tab_widget.setStyleSheet("""
            QTabWidget::pane {
                border: none; background-color: #F7F8FA; border-radius:6px;
            }
            QTabBar::tab {
                font-family: "Microsoft YaHei";
                padding: 10px 24px; font-size:14px;
                min-width: 126px; min-height:36px;
                background-color: #E8F3FF; color:#2A7FFF;
                border-radius:6px; margin-right:8px;
            }
            QTabBar::tab:selected {
                background-color: #2A7FFF; color:white; font-weight:bold;
            }
        """)
        self.tab_widget.tabBar().setElideMode(Qt.ElideNone)
        self.tab_widget.tabBar().setUsesScrollButtons(True)

        # 1. 原来的折线图子页面
        self.chart_page = QWidget()
        self.init_chart_page()
        self.tab_widget.addTab(self.chart_page, "📈 监测数据折线图")

        # 2. 新增：浸润线图子页面（黑色背景）
        self.seepage_page = QWidget()
        self.init_seepage_page()
        self.tab_widget.addTab(self.seepage_page, "💧 坝体浸润线图")
        self.flood_page = QWidget()
        self.init_flood_page()
        self.tab_widget.addTab(self.flood_page, "🌊 调洪演算")

        layout.addWidget(self.tab_widget)

    # 初始化折线图页面（保持原来功能不变）
    def init_chart_page(self):
        layout = QVBoxLayout(self.chart_page)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(16)

        select_card = QWidget()
        select_card.setStyleSheet("""
            QWidget {
                background-color: white;
                border-radius: 8px;
                border: 1px solid #E5E6EB;
            }
        """)
        select_layout = QHBoxLayout(select_card)
        select_layout.setContentsMargins(20, 18, 20, 18)
        select_layout.setSpacing(16)

        self.cb_type = QComboBox()
        self.cb_type.addItems(ALL_TABLES)
        self.cb_type.setStyleSheet("""
            QComboBox {
                border: 1px solid #E5E6EB;
                border-radius: 6px;
                padding: 10px 12px;
                font-size: 14px;
                color: #4E5969;
                background-color: white;
                min-height: 40px;
            }
            QComboBox::drop-down { border: none; width: 20px; }
            QComboBox QAbstractItemView {
                border: 1px solid #E5E6EB;
                border-radius: 6px;
                background-color: white;
                selection-background-color: #E8F3FF;
                selection-color: #2A7FFF;
            }
        """)
        self.cb_type.currentTextChanged.connect(self.load_instruments)
        select_layout.addWidget(QLabel("仪器类型："))
        select_layout.addWidget(self.cb_type)

        self.cb_dev = QComboBox()
        self.cb_dev.addItem("请选择仪器编号")
        self.cb_dev.setStyleSheet("""
            QComboBox {
                border: 1px solid #E5E6EB;
                border-radius: 6px;
                padding: 10px 12px;
                font-size: 14px;
                color: #4E5969;
                background-color: white;
                min-height: 40px;
            }
            QComboBox::drop-down { border: none; width: 20px; }
            QComboBox QAbstractItemView {
                border: 1px solid #E5E6EB;
                border-radius: 6px;
                background-color: white;
                selection-background-color: #E8F3FF;
                selection-color: #2A7FFF;
            }
        """)
        select_layout.addWidget(QLabel("仪器编号："))
        select_layout.addWidget(self.cb_dev)

        btn_chart = QPushButton("生成折线图")
        btn_chart.clicked.connect(self.generate_chart)
        btn_chart.setStyleSheet("""
            QPushButton {
                background-color: #2A7FFF;
                color: white;
                border-radius: 6px;
                padding: 10px 20px;
                font-size: 14px;
                font-weight: bold;
                border: none;
                min-height: 40px;
            }
            QPushButton:hover { background-color: #4096ff; }
            QPushButton:pressed { background-color: #1b6aff; }
        """)
        select_layout.addWidget(btn_chart)

        layout.addWidget(select_card)

        chart_card = QWidget()
        chart_card.setStyleSheet("""
            QWidget {
                background-color: white;
                border-radius: 8px;
                border: 1px solid #E5E6EB;
            }
        """)
        chart_layout = QVBoxLayout(chart_card)
        chart_layout.setContentsMargins(14, 14, 14, 14)

        self.chart_view = QChartView()
        self.chart_view.setRenderHint(QPainter.Antialiasing)
        chart_layout.addWidget(self.chart_view)
        layout.addWidget(chart_card)

        self.load_instruments(self.cb_type.currentText())

    # 初始化浸润线图页面（白色背景）
    def init_seepage_page(self):
        layout = QVBoxLayout(self.seepage_page)
        layout.setContentsMargins(0, 0, 0, 0)


        container = QWidget()
        container.setStyleSheet("background-color: #FFFFFF; border-radius: 8px;")
        container_layout = QVBoxLayout(container)
        container_layout.setContentsMargins(10,10,10,10)

        # 浸润线图查看器（和PDF查看器一样可缩放拖动）
        self.seepage_view = QGraphicsView()
        self.seepage_view.setRenderHint(QPainter.Antialiasing)
        self.seepage_view.setDragMode(QGraphicsView.ScrollHandDrag)
        self.seepage_view.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
        self.seepage_view.setResizeAnchor(QGraphicsView.AnchorUnderMouse)
        self.seepage_view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.seepage_view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.seepage_scene = QGraphicsScene()
        self.seepage_view.setScene(self.seepage_scene)

        # 加载浸润线图（用你截图里的路径）
        self.load_seepage_image()

        container_layout.addWidget(self.seepage_view)
        layout.addWidget(container)

    # 加载浸润线图
    def load_seepage_image(self):
        # 自动获取桌面路径
        desktop = os.path.join(os.path.expanduser("~"), "Desktop")
        seepage_path = os.path.join(desktop, "毕业设计", "打印PDF或png", "浸润线1.png")  # 假设你把图保存为这个文件名

        try:
            pixmap = QPixmap(seepage_path)
            if pixmap.isNull():
                raise Exception("图片文件无效")
            self.seepage_scene.clear()
            self.seepage_scene.addPixmap(pixmap)
            self.seepage_view.resetTransform()
            self.seepage_view.scale(0.5, 0.5)  # 默认缩小到50%
        except Exception as e:
            self.seepage_scene.clear()
            text = self.seepage_scene.addText(f"浸润线图加载失败：{str(e)}")
            text.setDefaultTextColor(QColor(0,0,0))  #

    # 滚轮缩放浸润线图
    def wheelEvent(self, event):
        if self.tab_widget.currentIndex() == 1:  # 浸润线页面
            if event.angleDelta().y() > 0:
                self.seepage_view.scale(1.2, 1.2)
            else:
                self.seepage_view.scale(0.8, 0.8)
        else:
            super().wheelEvent(event)

    # ===================== 调洪演算 =====================
    def init_flood_page(self):
        layout = QVBoxLayout(self.flood_page)
        layout.setSpacing(15)

        # 按钮
        btn_layout = QHBoxLayout()
        btn_layout.setSpacing(12)
        self.btn_import = QPushButton("导入洪水过程数据")
        self.btn_import.clicked.connect(self.import_flood_data)
        self.btn_import.setStyleSheet("font: 14px 'Microsoft YaHei'; padding:10px 24px; min-height:36px; min-width:210px; background:#4CAF50; color:white; border-radius:6px")

        self.btn_run = QPushButton("开始调洪演算")
        self.btn_run.clicked.connect(self.run_flood_analysis)
        self.btn_run.setStyleSheet("font: 14px 'Microsoft YaHei'; padding:10px 24px; min-height:36px; min-width:210px; background:#FF5722; color:white; border-radius:6px")

        self.btn_check = QPushButton("水量平衡校核")
        self.btn_check.clicked.connect(self.check_balance)
        self.btn_check.setStyleSheet("font: 14px 'Microsoft YaHei'; padding:10px 24px; min-height:36px; min-width:210px; background:#2A7FFF; color:white; border-radius:6px")
        self.btn_check.setEnabled(False)

        btn_layout.addWidget(self.btn_import)
        btn_layout.addWidget(self.btn_run)
        btn_layout.addWidget(self.btn_check)
        layout.addLayout(btn_layout)

        # 日志
        self.log_text = QTextEdit()
        self.log_text.setMaximumHeight(120)
        layout.addWidget(self.log_text)

        # 滚动窗口（能完整显示图片 + 滚轮下滑）
        self.scroll = QScrollArea()
        self.scroll.setWidgetResizable(True)
        self.chart_widget = QWidget()
        self.chart_layout = QVBoxLayout(self.chart_widget)
        self.chart_layout.setContentsMargins(8, 8, 8, 8)
        self.chart_layout.setSpacing(14)
        self.scroll.setWidget(self.chart_widget)
        layout.addWidget(self.scroll)

        self.flood_data = None

    def import_flood_data(self):
        path, _ = QFileDialog.getOpenFileName(self, "选择文件", "", "CSV/Excel(*.csv *.xlsx)")
        if not path:
            return
        try:
            if path.endswith('.csv'):
                # 自动识别编码，解决中文乱码问题
                try:
                    df = pd.read_csv(path, encoding='utf-8')
                except UnicodeDecodeError:
                    df = pd.read_csv(path, encoding='gbk')
            else:
                df = pd.read_excel(path)
            df.columns = ['时间(h)', '流量(m3/s)']
            self.times = df.iloc[:, 0].astype(int).tolist()
            self.qs = df.iloc[:, 1].astype(float).tolist()
            self.flood_data = df
            self._append_log(f"✅ 导入成功：{len(self.times)} 组数据")
        except Exception as e:
            self._append_log("❌ 导入失败：" + str(e))

    def run_flood_analysis(self):
        if not self._qt_alive(self):
            return
        if not self._qt_alive(getattr(self, 'log_text', None)):
            return
        if self.flood_data is None:
            self._append_log("❌ 请先导入数据")
            return
        try:
            self._append_log("🔍 开始调洪演算...")
            self.calc_flood()
        except Exception as e:
            self._append_log("❌ 失败：" + str(e))

    import matplotlib.pyplot as plt
    plt.rcParams["font.sans-serif"] = ["SimHei", "WenQuanYi Zen Hei"]
    plt.rcParams["axes.unicode_minus"] = False

    def _qt_alive(self, obj):
        try:
            return obj is not None and not sip.isdeleted(obj)
        except Exception:
            return obj is not None

    def _append_log(self, text):
        log_text = getattr(self, 'log_text', None)
        if not self._qt_alive(log_text):
            return
        try:
            log_text.append(text)
        except RuntimeError:
            pass

    def _chart_layout_alive(self):
        return self._qt_alive(getattr(self, 'chart_layout', None)) and self._qt_alive(getattr(self, 'chart_widget', None))

    def _safe_draw_idle(self, canvas):
        try:
            if self._qt_alive(canvas) and self._qt_alive(self) and canvas.parent() is not None:
                canvas.draw_idle()
        except RuntimeError:
            pass

    def _clear_mpl_canvases(self):
        if not self._qt_alive(self):
            return
        if hasattr(self, 'mpl_connections'):
            for item in self.mpl_connections:
                try:
                    canvas, cid = item
                    if self._qt_alive(canvas):
                        canvas.mpl_disconnect(cid)
                except Exception:
                    try:
                        plt.disconnect(item)
                    except Exception:
                        pass
            self.mpl_connections.clear()

        old_canvases = []
        if hasattr(self, 'mpl_canvases'):
            old_canvases = list(self.mpl_canvases)
            for canvas in old_canvases:
                if not self._qt_alive(canvas):
                    continue
                try:
                    canvas.setUpdatesEnabled(False)
                except RuntimeError:
                    pass
                try:
                    canvas._draw_pending = False
                except Exception:
                    pass
                try:
                    canvas.hide()
                except RuntimeError:
                    pass
                try:
                    canvas.setParent(None)
                except RuntimeError:
                    pass
                try:
                    canvas.figure.clear()
                except Exception:
                    pass
            self.mpl_canvases.clear()

        if self._chart_layout_alive():
            while self.chart_layout.count() > 0:
                item = self.chart_layout.takeAt(0)
                widget = item.widget()
                if self._qt_alive(widget):
                    try:
                        widget.hide()
                        widget.setParent(None)
                    except RuntimeError:
                        pass

        if old_canvases:
            self._retired_mpl_canvases.extend(old_canvases)
            if len(self._retired_mpl_canvases) > 20:
                self._retired_mpl_canvases = self._retired_mpl_canvases[-20:]

    def calc_flood(self):
        if not hasattr(self, 'mpl_connections'):
            self.mpl_connections = []
        if not hasattr(self, 'mpl_canvases'):
            self.mpl_canvases = []
        if not hasattr(self, '_retired_mpl_canvases'):
            self._retired_mpl_canvases = []
        if not self._qt_alive(self) or not self._chart_layout_alive():
            return

        import numpy as np
        import matplotlib.pyplot as plt
        from scipy.interpolate import Akima1DInterpolator, PchipInterpolator

        # ========== 1. 基础配置与变量定义（补全缺失部分） ==========
        plt.rcParams["font.sans-serif"] = ["SimHei"]
        plt.rcParams["axes.unicode_minus"] = False

        # 水位-库容特性曲线
        level_data = np.array([2265.00, 2288.05, 2289.44, 2290.37, 2291.80])
        storage_data_wan = np.array([98.43, 541.96, 582.08, 609.81, 654.74])

        x_smooth = np.linspace(level_data[0], level_data[-1], 300)
        akima_curve = Akima1DInterpolator(level_data, storage_data_wan)
        y_smooth = akima_curve(x_smooth)

        v2z_interp = PchipInterpolator(storage_data_wan, level_data, extrapolate=True)
        z2v_interp = Akima1DInterpolator(level_data, storage_data_wan)

        def V2Z(V):
            return float(v2z_interp(V / 10000))

        def Z2V(Z):
            return float(z2v_interp(Z)) * 10000

        # 控制参数
        Z_NORMAL = 2288.05  # 正常蓄水位
        Z_DESIGN = 2290.37  # 设计洪水位
        Z_CHECK = 2291.80  # 校核洪水位
        Q_SAFE = 88.0  # 安全泄量
        Q_DESIGN = 178.01  # 设计控泄流量
        Q_MAX_FREE = 365.81  # 闸门全开最大泄量
        V_min = storage_data_wan[0] * 10000  # 死库容 98.43万 m³
        dt = 3600  # 整小时步长（秒）
        g = 9.81
        B = 24
        m = 0.49

        def free_discharge(Z):
            if Z <= Z_NORMAL:
                return 0.0
            H = Z - Z_NORMAL
            q = m * B * np.sqrt(2 * g) * H ** 1.5
            return min(q, Q_MAX_FREE)

        def discharge_controlled(Z, Q_in):
            """
            严格遵循水利规范的闸门控制逻辑：
            1.  优先级：校核洪水位 > 设计洪水位 > 正常蓄水位
            2.  下泄流量由【当前时刻水位】决定，不随迭代水位跳变
            3.  严格按你的要求：
                - 水位=正常蓄水位 + 来水≤88 → 泄水=来水
                - 水位>正常蓄水位 → 无论来水多少，最低按88下泄
                - 水位≥设计洪水位 → 固定178.01
                - 水位≥校核洪水位 → 闸门全开自由泄流
            """
            # 浮点精度容错，避免判断错误
            Z = round(Z, 3)
            Z_NORMAL_R = round(Z_NORMAL, 3)
            Z_DESIGN_R = round(Z_DESIGN, 3)
            Z_CHECK_R = round(Z_CHECK, 3)

            # 1. 最高优先级：校核洪水位以上，闸门全开
            if Z >= Z_CHECK_R:
                return free_discharge(Z)

            # 2. 第二优先级：设计洪水位~校核洪水位之间，固定178.01
            if Z >= Z_DESIGN_R:
                return Q_DESIGN

            # 3. 第三优先级：正常蓄水位~设计洪水位之间，固定88
            if Z > Z_NORMAL_R:
                return Q_SAFE

            # 4. 最低优先级：水位正好在正常蓄水位，按来水控制
            if Z == Z_NORMAL_R:
                if Q_in <= Q_SAFE:
                    return Q_in  # 来水≤88，来多少泄多少
                else:
                    return Q_SAFE  # 来水>88，按88控泄

            # 水位低于正常蓄水位，无泄流
            return 0.0

        # 入库洪水过程（直接使用导入的洪水过程数据）
        if self.flood_data is None or len(self.flood_data) < 2:
            self._append_log("❌ 请先导入有效的洪水过程数据（至少 2 个时间点）")
            return
        self.times_hour = self.flood_data.iloc[:, 0].astype(float).to_numpy()
        self.qs_hour = self.flood_data.iloc[:, 1].astype(float).to_numpy()

        # ========== 2. 初始化结果列表（会插入中间穿越时刻） ==========
        t_list = [float(self.times_hour[0])]  # 时间列表（小时），与导入数据起点一致
        q_in_list = [float(self.qs_hour[0])]
        q_out_list = [float(self.qs_hour[0])]
        v_list = [Z2V(Z_NORMAL)]
        z_list = [Z_NORMAL]

        # 阈值库容（m³）
        V_design_target = Z2V(Z_DESIGN)
        V_check_target = Z2V(Z_CHECK)

        # ========== 3. 逐时段试算（带阈值内插，优化自你提供的代码） ==========
        for i in range(len(self.times_hour) - 1):
            Q1 = self.qs_hour[i]
            Q2 = self.qs_hour[i + 1]
            V_start = v_list[-1]
            Z_start = z_list[-1]
            q_start = q_out_list[-1]
            t_hour_start = self.times_hour[i]
            t_hour_end = self.times_hour[i + 1]
            # 前期：水位在正常蓄水位，且时段内来水全程≤88 → 来多少泄多少，水位不变
            # 前期：水位严格在正常蓄水位，且时段内最大来水≤88 → 全程来多少泄多少，水位纹丝不动
            if abs(Z_start - Z_NORMAL) < 0.001 and max(Q1, Q2) <= Q_SAFE:
                t_list.append(t_hour_end)
                q_in_list.append(Q2)
                q_out_list.append(Q2)
                v_list.append(V_start)  # 库容完全不变
                z_list.append(Z_start)  # 水位完全不变
                continue

            # ---------- 辅助函数：严格水量平衡
            def solve_sub_interval(V_begin, Z_begin, q_begin, t_begin_sec, t_end_sec):
                dt_sub = t_end_sec - t_begin_sec
                if dt_sub <= 0:
                    return V_begin, Z_begin, q_begin

                t_mid_sec = (t_begin_sec + t_end_sec) / 2.0
                Q_avg = Q1 + (Q2 - Q1) * (t_mid_sec / dt)

                V_min = storage_data_wan[0] * 10000  # 死库容
                q_guess = q_begin
                for _ in range(50):
                    V_new = V_begin + (Q_avg - (q_begin + q_guess) / 2.0) * dt_sub
                    if V_new < V_min:
                        V_new = V_min
                    Z_new = V2Z(V_new)
                    q_new = discharge_controlled(Z_new, Q_avg)
                    if abs(q_new - q_guess) < 0.01:
                        break
                    q_guess = q_new
                else:
                    q_new = q_guess
                return V_new, Z_new, q_new
            # ---------- 先假设无穿越，计算全时段 ----------
            V_end_full, Z_end_full, q_end_full = solve_sub_interval(V_start, Z_start, q_start, 0.0, dt)

            need_redesign = (Z_start < Z_DESIGN) and (Z_end_full >= Z_DESIGN)
            need_recheck = (Z_start < Z_CHECK) and (Z_end_full >= Z_CHECK)

            # ---------- 无穿越：直接保存 ----------
            # ---------- 无穿越：严格按水量平衡计算后保存 ----------
            if not need_redesign and not need_recheck:
                # 重新计算全时段，确保水量平衡100%正确
                V_end_full, Z_end_full, q_end_full = solve_sub_interval(V_start, Z_start, q_start, 0.0, dt)
                t_list.append(t_hour_end)
                q_in_list.append(Q2)
                q_out_list.append(q_end_full)
                v_list.append(V_end_full)
                z_list.append(Z_end_full)
                continue

            # ---------- 处理设计洪水位穿越 ----------
            if need_redesign:
                q_old = Q_SAFE
                # 解二次方程求穿越时刻比例 alpha
                A = (Q2 - Q1) / 2.0 * dt
                B = (Q1 - q_old) * dt
                C = V_start - V_design_target
                alpha = 0.0
                if abs(A) < 1e-9:
                    if abs(B) > 1e-9:
                        alpha = -C / B
                else:
                    disc = B * B - 4 * A * C
                    if disc >= 0:
                        sqrt_disc = np.sqrt(disc)
                        a1 = (-B + sqrt_disc) / (2 * A)
                        a2 = (-B - sqrt_disc) / (2 * A)
                        if 0.0 < a1 <= 1.0:
                            alpha = a1
                        elif 0.0 < a2 <= 1.0:
                            alpha = a2
                alpha = np.clip(alpha, 0.001, 0.999)

                t_cross_sec = alpha * dt
                t_cross_hour = t_hour_start + t_cross_sec / 3600
                q_in_cross = Q1 + (Q2 - Q1) * alpha

                # 保存设计洪水位穿越点
                # 保存设计洪水位穿越点（平滑过渡）
                # 设计洪水位穿越点：连续计算，不强制锁定库容，保证水量平衡
                V_x, Z_x, q_x = solve_sub_interval(V_start, Z_start, q_start, 0.0, t_cross_sec)

                # 仅锁定下泄流量，不修改库容/水位，保证水量平衡
                q_x = Q_DESIGN
                t_list.append(t_cross_hour)
                q_in_list.append(q_in_cross)
                q_out_list.append(q_x)
                v_list.append(V_x)
                z_list.append(Z_x)

                # 检查后半段是否穿越校核
                V_rem_end, Z_rem_end, q_rem_end = solve_sub_interval(V_x, Z_x, q_x, t_cross_sec, dt)
                need_recheck_in_rem = (Z_x < Z_CHECK) and (Z_rem_end >= Z_CHECK)

                if need_recheck and need_recheck_in_rem:
                    # 二级内插：后半段找校核穿越点
                    q_old2 = Q_DESIGN
                    dt2 = dt - t_cross_sec
                    V_start2 = V_x
                    Q_at_tcross = q_in_cross
                    A2 = (Q2 - Q_at_tcross) / 2.0 * dt2
                    B2 = (Q_at_tcross - q_old2) * dt2
                    C2 = V_start2 - V_check_target
                    beta = 0.0
                    if abs(A2) < 1e-9:
                        if abs(B2) > 1e-9:
                            beta = -C2 / B2
                    else:
                        disc2 = B2 * B2 - 4 * A2 * C2
                        if disc2 >= 0:
                            sqrt_disc2 = np.sqrt(disc2)
                            b1 = (-B2 + sqrt_disc2) / (2 * A2)
                            b2 = (-B2 - sqrt_disc2) / (2 * A2)
                            if 0.0 < b1 <= 1.0:
                                beta = b1
                            elif 0.0 < b2 <= 1.0:
                                beta = b2
                    beta = np.clip(beta, 0.001, 0.999)
                    t_cross2_sec = t_cross_sec + beta * dt2
                    t_cross2_hour = t_hour_start + t_cross2_sec / 3600
                    q_in_cross2 = Q_at_tcross + (Q2 - Q_at_tcross) * beta

                    # 保存校核洪水位穿越点
                    # 校核洪水位穿越点：连续计算，不强制锁定库容
                    V_z, Z_z, q_z = solve_sub_interval(V_x, Z_x, q_x, t_cross_sec, t_cross2_sec)
                    # 仅锁定下泄流量，不修改库容/水位
                    q_z = free_discharge(Z_CHECK)
                    t_list.append(t_cross2_hour)
                    q_in_list.append(q_in_cross2)
                    q_out_list.append(q_z)
                    v_list.append(V_z)
                    z_list.append(Z_z)

                    # 最后一段：校核到时段末
                    V_final, Z_final, q_final = solve_sub_interval(V_z, Z_z, q_z, t_cross2_sec, dt)
                    t_list.append(t_hour_end)
                    q_in_list.append(Q2)
                    q_out_list.append(q_final)
                    v_list.append(V_final)
                    z_list.append(Z_final)
                    continue

                else:
                    # 后半段无校核穿越，直接保存
                    t_list.append(t_hour_end)
                    q_in_list.append(Q2)
                    q_out_list.append(q_rem_end)
                    v_list.append(V_rem_end)
                    z_list.append(Z_rem_end)
                    continue

            # ---------- 仅校核洪水位穿越（用二分法） ----------
            if need_recheck and not need_redesign:
                t_left = 0.0
                t_right = dt
                for _ in range(30):
                    t_mid = (t_left + t_right) / 2.0
                    V_mid, Z_mid, _ = solve_sub_interval(V_start, Z_start, q_start, 0.0, t_mid)
                    if Z_mid < Z_CHECK:
                        t_left = t_mid
                    else:
                        t_right = t_mid
                t_cross_c_sec = (t_left + t_right) / 2.0
                t_cross_c_hour = t_hour_start + t_cross_c_sec / 3600
                q_in_cross_c = Q1 + (Q2 - Q1) * (t_cross_c_sec / dt)

                V_c, Z_c, q_c = solve_sub_interval(V_start, Z_start, q_start, 0.0, t_cross_c_sec)
                # 不覆盖 q_c，不覆盖 V_c/Z_c
                t_list.append(t_cross_c_hour)
                q_in_list.append(q_in_cross_c)
                q_out_list.append(q_c)
                v_list.append(V_c)  # 真实计算值
                z_list.append(Z_c)  # 真实计算值
                # 最后一段
                V_final, Z_final, q_final = solve_sub_interval(V_check_target, Z_CHECK, q_c, t_cross_c_sec, dt)
                t_list.append(t_hour_end)
                q_in_list.append(Q2)
                q_out_list.append(q_final)
                v_list.append(V_final)
                z_list.append(Z_final)
                continue

        balance_v_list = [Z2V(Z_NORMAL)]
        balance_z_list = [Z_NORMAL]
        for i in range(1, len(t_list)):
            dt_actual = (t_list[i] - t_list[i - 1]) * 3600
            net_flow = ((q_in_list[i - 1] - q_out_list[i - 1]) + (q_in_list[i] - q_out_list[i])) / 2
            V_next = balance_v_list[-1] + net_flow * dt_actual
            V_next = max(V_min, V_next)
            balance_v_list.append(V_next)
            balance_z_list.append(V2Z(V_next))

        # ========== 5. 赋值给UI ==========
        self.times = np.array(t_list)
        self.qs = np.array(q_in_list)
        self.Qout = np.array(q_out_list)
        self.Z_out = np.array(balance_z_list)
        self.V_out = np.array(balance_v_list)

        # ===================== 清理旧画布 =====================
        self._clear_mpl_canvases()
        if not self._chart_layout_alive():
            return

        # ==========================================================================
        # 图1：调洪演算结果
        # ==========================================================================
        fig1 = Figure(figsize=(10.5, 5.2), dpi=100)
        canvas1 = FigureCanvas(fig1)
        canvas1.setMinimumHeight(430)
        ax1 = fig1.add_subplot(111)

        ax1.set_xlabel("时间 (h)")
        ax1.set_ylabel(r"流量 (m$^3$/s)", color='r')
        ax1.plot(self.times, self.qs, 'r-', linewidth=2.3, label='入库流量')
        ax1.plot(self.times, self.Qout, 'orange', linewidth=2.3, label='下泄流量')
        ax1.legend(loc='upper left')

        ax2 = ax1.twinx()
        ax2.set_ylabel("库水位 (m)", color='blue')
        # 光滑水位曲线
        t_smooth = np.linspace(self.times.min(), self.times.max(), 1000)
        z_smooth_interp = PchipInterpolator(self.times, self.Z_out)
        z_smooth = z_smooth_interp(t_smooth)
        ax2.plot(t_smooth, z_smooth, 'b-', linewidth=2.3, label='库水位')
        ax2.legend(loc='upper right')

        max_z = max(self.Z_out)
        max_q = max(self.Qout)
        fig1.suptitle(f"调洪演算 | 最高水位 {max_z:.2f}m | 最大下泄 {max_q:.2f}m$^3$/s", fontsize=14, fontweight='bold')
        fig1.subplots_adjust(left=0.08, right=0.92, top=0.88, bottom=0.14)

        # 鼠标交互
        vline = ax1.axvline(color='gray', ls='--', lw=1, visible=False)
        tip1 = ax1.annotate("", xy=(0, 0), xytext=(10, 10),
                            textcoords="offset points",
                            bbox=dict(boxstyle="round,pad=0.8", fc="white", ec="blue", alpha=0.9),
                            arrowprops=dict(arrowstyle="->", color='gray'))
        tip1.set_visible(False)

        def on_move1(e):
            if not hasattr(canvas1, 'renderer') or e.inaxes not in [ax1, ax2]:
                vline.set_visible(False)
                tip1.set_visible(False)
                self._safe_draw_idle(canvas1)
                return

            x = e.xdata
            if x is None:
                return
            idx = np.clip(np.argmin(np.abs(self.times - x)), 0, len(self.times) - 1)
            t = self.times[idx]
            qi = self.qs[idx]
            qo = self.Qout[idx]
            zl = self.Z_out[idx]
            vline.set_xdata([t, t])
            vline.set_visible(True)
            tip1.xy = (t, qi)
            tip1.set_text(f"时间：{t:.2f}h\n入库：{qi:.1f}\n下泄：{qo:.1f}\n水位：{zl:.2f}")
            tip1.set_visible(True)
            self._safe_draw_idle(canvas1)

        cid1 = canvas1.mpl_connect('motion_notify_event', on_move1)
        self.mpl_connections.append((canvas1, cid1))
        self.mpl_canvases.append(canvas1)
        if not self._chart_layout_alive():
            return
        self.chart_layout.addWidget(canvas1)

        # ==========================================================================
        # 图2：水位-库容曲线
        # ==========================================================================
        fig2 = Figure(figsize=(10.5, 4.2), dpi=100)
        canvas2 = FigureCanvas(fig2)
        canvas2.setMinimumHeight(350)
        ax3 = fig2.add_subplot(111)

        # 重生成平滑曲线，防止变量污染
        x_smooth_plot = np.linspace(level_data[0], level_data[-1], 300)
        y_smooth_plot = akima_curve(x_smooth_plot)

        ax3.plot(x_smooth_plot, y_smooth_plot, 'b-', linewidth=3, label='水位-库容曲线')
        ax3.scatter(level_data, storage_data_wan, color='red', s=80, zorder=5)

        ax3.annotate('死水位 2265m', (2265, 98.43), xytext=(2263, 150),
                     arrowprops=dict(arrowstyle='->', color='darkred'))
        ax3.annotate('正常蓄水位 2288.05m', (2288.05, 541.96), xytext=(2285, 600),
                     arrowprops=dict(arrowstyle='->', color='darkred'))
        ax3.annotate('设计洪水位 2290.37m', (2290.37, 609.81), xytext=(2288, 660),
                     arrowprops=dict(arrowstyle='->', color='darkred'))
        ax3.annotate('校核洪水位 2291.8m', (2291.8, 654.74), xytext=(2290, 630),
                     arrowprops=dict(arrowstyle='->', color='darkred'))

        ax3.set_xlabel("水位 (m)")
        ax3.set_ylabel(r"库容 (万m$^3$)")
        ax3.set_title("水库水位-库容特性曲线")
        ax3.grid(True, linestyle='--', alpha=0.7)
        ax3.legend(loc='lower right')
        fig2.subplots_adjust(left=0.08, right=0.98, top=0.86, bottom=0.16)

        tip2 = ax3.annotate("", xy=(0, 0), xytext=(10, 10),
                            textcoords="offset points",
                            bbox=dict(boxstyle="round,pad=0.5", fc="white", ec="blue", alpha=0.8),
                            arrowprops=dict(arrowstyle="->"))
        tip2.set_visible(False)

        def on_move2(e):
            if e.inaxes != ax3:
                tip2.set_visible(False)
                self._safe_draw_idle(canvas2)
                return

            xd, yd = e.xdata, e.ydata
            if xd is None or yd is None:
                return
            idx = np.argmin(np.hypot(x_smooth_plot - xd, y_smooth_plot - yd))
            tip2.xy = (x_smooth_plot[idx], y_smooth_plot[idx])
            tip2.set_text(f"水位：{x_smooth_plot[idx]:.2f}m\n库容：{y_smooth_plot[idx]:.2f}万m3")
            tip2.set_visible(True)
            self._safe_draw_idle(canvas2)

        cid2 = canvas2.mpl_connect('motion_notify_event', on_move2)
        self.mpl_connections.append((canvas2, cid2))
        self.mpl_canvases.append(canvas2)
        if not self._chart_layout_alive():
            return
        self.chart_layout.addWidget(canvas2)

        if self._qt_alive(getattr(self, 'btn_check', None)):
            self.btn_check.setEnabled(True)
        self._append_log(f"\n✅ 调洪演算完成")
        self._append_log(f"🌊 最高洪水位：{max_z:.2f} m")
        self._append_log(f"💧 最大下泄流量：{max_q:.2f} m3/s")

    def check_balance(self):
        if not hasattr(self, 'Qout') or self.Qout is None or len(self.qs) < 2:
            self._append_log("❌ 未进行调洪演算或数据不足")
            return

        self._append_log("\n【水量平衡规范校核】")

        # ========== 1. 100%正确的总入库/总下泄计算（梯形法，适配任意时间间隔） ==========
        total_in_m3 = 0.0  # 单位：m³
        total_out_m3 = 0.0  # 单位：m³
        for i in range(len(self.qs) - 1):
            # 实际时间间隔（秒），适配内插的非整小时点
            dt_actual = (self.times[i + 1] - self.times[i]) * 3600
            # 梯形法计算时段水量，单位：m³
            total_in_m3 += (self.qs[i] + self.qs[i + 1]) / 2 * dt_actual
            total_out_m3 += (self.Qout[i] + self.Qout[i + 1]) / 2 * dt_actual

        # ========== 2. 自动适配库容单位，计算库容变化 ==========
        V_start = self.V_out[0]
        V_end = self.V_out[-1]
        # 自动判断单位：如果V_start<1000，说明是万m³；否则是m³
        if V_start < 1000:
            # 库容单位是万m³，转成m³和总入库/下泄统一
            storage_m3 = (V_end - V_start) * 10000
        else:
            # 库容单位已经是m³，直接用
            storage_m3 = V_end - V_start

        # ========== 3. 水量平衡误差计算（水利规范标准公式） ==========
        # 规范公式：总入库 - 总下泄 = 库容变化
        error_m3 = abs(total_in_m3 - total_out_m3 - storage_m3)
        allow_m3 = 5000  # 水利规范允许误差：5000 m³（0.5万m³）

        # ========== 4. 统一转万m³输出，符合工程习惯 ==========
        self._append_log(f"总入库：{total_in_m3 / 10000:.2f} 万m³")
        self._append_log(f"总下泄：{total_out_m3 / 10000:.2f} 万m³")
        self._append_log(f"库容变化：{storage_m3 / 10000:.2f} 万m³")
        self._append_log(f"计算误差：{error_m3 / 10000:.3f} 万m³")

        if error_m3 < allow_m3:
            self._append_log("✅ 水量平衡校核通过，符合水利规范")
        else:
            self._append_log("❌ 水量平衡不满足，调洪演算逻辑存在错误")

    # 折线图相关函数（保持不变）
    def load_instruments(self, table_name):
        self.cb_dev.clear()
        self.cb_dev.addItem("请选择仪器编号")
        try:
            conn = sqlite3.connect(DB_PATH)
            cur = conn.cursor()
            cur.execute(f"SELECT 仪器编号 FROM `{table_name}`")
            rows = cur.fetchall()
            conn.close()
            for r in rows:
                self.cb_dev.addItem(r[0])
        except:
            pass

    def parse_date(self, date_str):
        try:
            if "年" in date_str and "月" in date_str and "日" in date_str:
                y, m, d = date_str.split("年")[0], date_str.split("年")[1].split("月")[0], date_str.split("月")[1].split("日")[0]
                return QDateTime.fromString(f"{y}-{m}-{d}", "yyyy-M-d")
            return QDateTime.fromString(date_str, "yyyy-MM-dd")
        except:
            return None

    def generate_chart(self):
        table = self.cb_type.currentText()
        dev_id = self.cb_dev.currentText().strip()

        if dev_id == "请选择仪器编号":
            QMessageBox.warning(self, "提示", "请选择仪器编号")
            return

        try:
            conn = sqlite3.connect(DB_PATH)
            cur = conn.cursor()
            cur.execute(f"PRAGMA table_info(`{table}`)")
            cols = [c[1] for c in cur.fetchall()]
            date_cols = [c for c in cols if "年" in c and "月" in c and "日" in c]
            cur.execute(f"SELECT * FROM `{table}` WHERE 仪器编号=?", (dev_id,))
            row = cur.fetchone()
            conn.close()

            if not row:
                QMessageBox.warning(self, "提示", "未找到该仪器数据")
                return

            series = QLineSeries()
            series.setName(f"{dev_id} 监测数据")
            valid_data = []
            for col in date_cols:
                idx = cols.index(col)
                val = row[idx]
                dt = self.parse_date(col)
                if val is not None and dt and dt.isValid():
                    valid_data.append((dt, float(val)))

            if not valid_data:
                QMessageBox.warning(self, "提示", "无有效日期数据")
                return

            valid_data.sort(key=lambda x: x[0])
            for dt, val in valid_data:
                series.append(dt.toMSecsSinceEpoch(), val)

            chart = QChart()
            chart.setTitle(f"{table} {dev_id} 监测数据趋势图")
            chart.legend().setVisible(True)
            chart.addSeries(series)

            axis_x = QDateTimeAxis()
            axis_x.setFormat("yyyy-MM-dd")
            axis_x.setTitleText("日期")
            axis_x.setMin(valid_data[0][0])
            axis_x.setMax(valid_data[-1][0])
            chart.addAxis(axis_x, Qt.AlignBottom)
            series.attachAxis(axis_x)

            axis_y = QValueAxis()
            axis_y.setTitleText("监测值")
            min_val = min(v for _, v in valid_data) * 0.9
            max_val = max(v for _, v in valid_data) * 1.1
            axis_y.setMin(min_val)
            axis_y.setMax(max_val)
            chart.addAxis(axis_y, Qt.AlignLeft)
            series.attachAxis(axis_y)

            pen = QPen(QColor("#2A7FFF"))
            pen.setWidth(3)
            series.setPen(pen)
            chart.setTheme(QChart.ChartThemeLight)
            chart.setBackgroundVisible(False)
            chart.setTitleFont(QFont("Microsoft YaHei", 14, QFont.Bold))

            self.chart_view.setChart(chart)
        except Exception as e:
            QMessageBox.critical(self, "错误", f"图表生成失败：{str(e)}")

# ===================== 主窗口（钉钉风格侧边栏） =====================
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("大坝智慧监测系统")
        self.setGeometry(100, 100, 1400, 800)
        self.setStyleSheet("""
            QMainWindow {
                background-color: #F5F7FA;
            }
        """)
        self.setFont(QFont("Microsoft YaHei"))

        # 侧边栏（完全钉钉风格）
        sidebar = QWidget()
        sidebar.setStyleSheet("""
            QWidget {
                background-color: #2A7FFF;
                border-right: 1px solid #1b6aff;
            }
        """)
        sidebar.setFixedWidth(200)
        sidebar_layout = QVBoxLayout(sidebar)
        sidebar_layout.setContentsMargins(0, 12, 0, 12)
        sidebar_layout.setSpacing(4)

        # 侧边栏按钮
        self.btns = {}
        pages = [
            ("🏠 系统首页", HomePage()),
            ("📝 数据存储", self.create_edit_tab()),
            ("🔍 数据查询", self.create_query_tab()),
            ("📊 仪器统计", InstrumentStatPage()),
            ("📈 数据分析", DataAnalysisPage())
        ]
        for name, page in pages:
            btn = QPushButton(name)
            btn.setStyleSheet("""
                QPushButton {
                    color: rgba(255,255,255,0.85);
                    background-color: transparent;
                    border: none;
                    text-align: left;
                    padding: 10px 20px;
                    font-size: 14px;
                    border-radius: 0;
                    min-height: 44px;
                }
                QPushButton:hover {
                    background-color: rgba(255,255,255,0.1);
                    color: white;
                }
                QPushButton:checked {
                    background-color: rgba(255,255,255,0.2);
                    color: white;
                    font-weight: bold;
                    border-left: 3px solid white;
                }
            """)
            btn.setCheckable(True)
            btn.setAutoExclusive(True)
            btn.clicked.connect(lambda checked, p=page: self.switch_page(p))
            sidebar_layout.addWidget(btn)
            self.btns[name] = btn

        sidebar_layout.addStretch()

        # 主内容区
        self.stack = QStackedWidget()
        self.stack.setStyleSheet("background-color: #F5F7FA;")
        for _, page in pages:
            self.stack.addWidget(page)

        # 主布局
        main_widget = QWidget()
        main_layout = QHBoxLayout(main_widget)
        main_layout.setContentsMargins(0, 0, 0, 0)
        main_layout.setSpacing(0)
        main_layout.addWidget(sidebar)
        main_layout.addWidget(self.stack)
        self.setCentralWidget(main_widget)

        # 默认选中第一个
        self.btns["🏠 系统首页"].setChecked(True)
        self.stack.setCurrentIndex(0)

    def create_edit_tab(self):
        tab = QTabWidget()
        tab.setStyleSheet(TAB_STYLE)
        tab.tabBar().setElideMode(Qt.ElideNone)
        tab.tabBar().setUsesScrollButtons(True)
        for t in ["渗压计", "土压力计", "水位计", "沉降仪", "测斜仪"]:
            tab.addTab(InstrumentEditPage(t), t)
        return tab

    def create_query_tab(self):
        tab = QTabWidget()
        tab.setStyleSheet(TAB_STYLE)
        tab.tabBar().setElideMode(Qt.ElideNone)
        tab.tabBar().setUsesScrollButtons(True)
        for t in ["渗压计", "土压力计", "水位计", "沉降仪", "测斜仪"]:
            tab.addTab(InstrumentQueryPage(t), t)
        return tab

    def switch_page(self, page):
        idx = self.stack.indexOf(page)
        self.stack.setCurrentIndex(idx)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    app.setStyle("Fusion")

    # 启动登录界面
    login = LoginWindow()

    # 登录成功后打开主界面
    def open_main():
        app.main_window = MainWindow()
        app.main_window.show()

    login.login_success.connect(open_main)
    login.show()

    sys.exit(app.exec_())
