吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1440|回复: 14
上一主题 下一主题
收起左侧

[Python 原创] 墨香密码管理器 v2.0 · 优化更新记录

[复制链接]
跳转到指定楼层
楼主
phantomxjc 发表于 2026-8-25 11:39 回帖奖励

墨香密码管理器 v2.0 · 优化更新记录

由于原文无法编辑:故而开新帖
原文链接:https://www.52pojie.cn/thread-2070353-1-1.html
注:项目代码优化有AI参与,本文文章内容大部分由AI撰写


一、优化总览

模块 改动内容 效果
基础健壮性 数据库/备份路径改用 BASE_DIR、统一图标加载、加高 DPI 适配 打包后数据不丢失、图标正常、4K 屏不糊
代码质量 删除重复方法定义与未调用死代码 结构更干净,维护更省心
密码生成器 新增 PasswordGeneratorDialog 一键生成强密码, cryptography 安全随机
空闲自动锁定 无操作可配置分钟数后自动锁屏 人走开防窥视,入口在「欢迎」菜单
数据导出 勾选记录 + 强制主密码 + 明文导出 可挑选导出、导出即明文本地可读
记住账号密码 两级记忆(只记账号 / 可选记密码) 平时只自动填账号,勾选才记密码
导出→导入闭环 导出列标准化、导入支持 CSV 导出文件可重新导回系统
登录界面 尺寸从 500×500 收紧至 420×460 布局更紧凑,输入框不再显胖

二、基础健壮性优化

1. 路径修复(最关键)
原代码数据库 password_manager.dbbackups/ 使用相对路径,打包后会在临时目录乱写。现统一改用 BASE_DIR(打包后指向 exe 所在目录,开发时指向脚本目录),数据乖乖待在 exe 旁边。

2. 图标修复
原代码查找 枫叶.png,但实际提供的是 枫叶.ico。新增统一 get_app_icon():优先加载 枫叶.ico,兼容打包环境,缺失时回退内置图标。

3. 高 DPI 适配
增加 AA_EnableHighDpiScaling,高分屏下界面不再模糊。

4. 清理冗余代码

  • 删除 on_table_changed / on_search / clear_search 的重复定义(原各写了两遍)
  • 删除从未被调用的死代码 reencrypt_all_passwords

三、新增功能详解

3.1 密码生成器

  • 新增 / 编辑记录时,密码框右侧多出「生成」按钮,一键弹出生成器。
  • 使用 secrets 密码学安全随机(非伪随机),长度 8–64 可调。
  • 大写 / 小写 / 数字 / 符号自由勾选,支持「重新生成 / 复制 / 使用此密码」。

3.2 空闲自动锁定(可配置)

  • 主窗口监听鼠标 / 键盘活动,无操作达到设定时间自动隐藏并退回登录界面(需主密码重新进入)。
  • 设定入口:右键右上角「欢迎,xxx」标签 → 「⏲ 空闲锁定设置」→ 数字框选 1–120 分钟,确定即生效。
  • 设置持久化到 mx_config.json(与 exe 同目录),下次打开自动记住。
  • 有模态弹窗(如编辑 / 导出对话框)打开时不会误锁。

3.3 数据导出

  • 控制面板「导出数据」→ 弹窗内列出记录,每条带勾选框,默认全选,支持「全选 / 全不选」单选。
  • 导出前必须输入主密码才能导出,导出的密码为明文(不加密),方便换机 / 审计。
  • 格式:Excel(多 sheet)/ CSV(带 utf-8-sig,Excel 打开中文不乱码)。

3.4 记住账号密码(两级)

登录界面两个勾选框:

  • 「记住账号」(默认勾选):下次自动填好账号,密码框留空,只需输密码。
  • 「记住密码」(默认不勾,未勾「记住账号」时禁用):额外勾选才把密码加密记住。

联动:取消「记住账号」时,「记住密码」自动禁用并取消勾选。

3.5 导出 → 导入闭环

改造前:导出带 id 列,而导入会重新分配 id,导致不闭环。
改造后

  • 导出列统一为:网站名称、网址、账号、密码、备注(去掉 id)。
  • Excel 导出:多 sheet,sheet 名为 单位表 / 个人表,导入时选对应 sheet + 目标表即可原样导回。
  • CSV 导出:单表 → 单个 .csv;全部 → .zip(含 单位表.csv + 个人表.csv)。
  • 导入功能新增 CSV 支持:文件过滤器改为 *.xlsx *.xls *.csv,选 CSV 时工作表下拉显示「CSV文件(无工作表)」。

3.6 登录界面尺寸优化

  • 窗口:500×500 → 420×460,更紧凑。
  • 标题 / 装饰花字号、内外边距、表单间距全部调小。
  • 账号密码框 QGroupBox 内边距 10px → 6px 10pxQLineEditmin/max-height 限制,框体不再显胖。

3.7 减小了打包后的体积

  • 原项目打包后500MB,过于臃肿,现项目打包压缩后不到60MB,大大降低了打包后的体积!

四、remember.json 字段说明

该文件仅在勾选「记住账号」/「记住密码」后生成,与 exe 同目录。

字段 含义
username 账号(明文,仅用于自动填充)
has_pwd true 表示同时记住密码;false 表示只记账号
nonce + cipher AES-GCM 加密后的密码(只记账号时为空)

密钥为程序内部固定的本地派生密钥(非用户主密码),他人拿到文件也无法解出密码。属本机级加密,要求更高安全时可手动删除该文件。


五、打包与交付

交付路径

H:\墨香密码管理器\
├── 墨香密码管理器.exe   # 双击即用
├── _internal\           # 运行时依赖(Qt5 / Crypto / pandas / sqlite3 等,已自包含)
└── backups\             # 自动备份目录

关键信息

  • 模式:onedir(文件夹),整个 墨香密码管理器 文件夹一起拷贝即用。
  • 体积:约 170 MB(已排除 matplotlib)。
  • 数据库位置:H:\墨香密码管理器\password_manager.db;沿用旧数据只需把原 password_manager.db 复制进该文件夹。
  • 单实例:靠 exe 固定路径判断,文件夹模式可正常工作。

打包环境说明(技术备忘)

  • 打包环境:系统 Python 3.12 + PyInstaller 7.x(onedir)。
  • 沙箱坑:本机沙箱拦截 PyInstaller 临时文件删除,通过将 workpath / distpath 指向系统临时目录绕过;交付 H 盘时用 PowerShell 强删旧目录再全新拷入,避免文件占用。

六、使用提示与后续可优化

当前已具备功能
密码生成器 · 空闲自动锁定(可配置)· 数据导出(勾选 + 主密码)· 记住账号 / 密码(两级)· 导出导入闭环 · 单实例 · 系统托盘 · 高 DPI 适配。




项目源码
[Python] 纯文本查看 复制代码
#version 2.0 by xujc 2026.1.22
import sys
import os
import hashlib
import sqlite3
import webbrowser
import secrets
import string
import time
import json
import zipfile
import io
import pandas as pd
from datetime import datetime
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
                             QLabel, QLineEdit, QPushButton, QTableWidget, QTableWidgetItem,
                             QCheckBox, QListWidget, QListWidgetItem,
                             QComboBox, QMessageBox, QDialog, QTextEdit, QGroupBox,
                             QHeaderView, QMenu, QAction, QFileDialog, QProgressBar,
                             QRadioButton, QButtonGroup, QGridLayout, QSpinBox, QSystemTrayIcon)
from PyQt5.QtCore import Qt, QEvent, QTimer, pyqtSignal, QThread, pyqtSlot, QSharedMemory
from PyQt5.QtGui import QFont, QColor, QPalette, QIcon
import openpyxl
import base64
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from Crypto.Protocol.KDF import PBKDF2

# 基础目录:打包后指向 exe 所在目录,开发时指向脚本目录
BASE_DIR = os.path.dirname(sys.executable) if getattr(sys, 'frozen', False) else os.path.dirname(os.path.abspath(__file__))


def _read_import_dataframe(file_path, sheet_name=None):
    """根据扩展名读取 Excel 或 CSV 为 DataFrame(供导入复用)"""
    ext = file_path.lower()
    if ext.endswith('.csv'):
        return pd.read_csv(file_path, encoding="utf-8-sig")
    if sheet_name:
        return pd.read_excel(file_path, sheet_name=sheet_name)
    return pd.read_excel(file_path)


# ===== 配置项(空闲自动锁定时间等,持久化到 exe 同目录)=====
CONFIG_FILE = os.path.join(BASE_DIR, "mx_config.json")
DEFAULT_IDLE_MINUTES = 5


def load_config():
    """读取本地配置,缺失或异常时返回空字典"""
    try:
        with open(CONFIG_FILE, "r", encoding="utf-8") as f:
            return json.load(f)
    except Exception:
        return {}


def save_config(cfg):
    """写入本地配置"""
    try:
        with open(CONFIG_FILE, "w", encoding="utf-8") as f:
            json.dump(cfg, f, ensure_ascii=False, indent=2)
    except Exception:
        pass


# ===== 记住账号密码(本地 AES-GCM 加密存储,非明文)=====
REMEMBER_FILE = os.path.join(BASE_DIR, "remember.json")
_REMEMBER_PASSPHRASE = "MoxiangLocalRemember_v1"
_REMEMBER_SALT = b"moxiang_remember_salt_2026"


def _remember_key():
    key, _ = DatabaseEncryption.derive_key(_REMEMBER_PASSPHRASE, _REMEMBER_SALT)
    return key


def save_remembered(username, remember_pwd=False, master_password=None):
    """保存记住的账号;remember_pwd=True 且提供密码时才连带加密保存密码。
    只记账号时密码字段留空(has_pwd=False),下次登录密码框为空。"""
    try:
        data = {"username": username, "has_pwd": False, "nonce": "", "cipher": ""}
        if remember_pwd and master_password:
            key = _remember_key()
            cipher = AES.new(key, AES.MODE_GCM)
            token = cipher.encrypt(master_password.encode("utf-8"))
            data["has_pwd"] = True
            data["nonce"] = base64.b64encode(cipher.nonce).decode()
            data["cipher"] = base64.b64encode(token).decode()
        with open(REMEMBER_FILE, "w", encoding="utf-8") as f:
            json.dump(data, f, ensure_ascii=False)
    except Exception:
        pass


def load_remembered():
    """返回 (username, password_or_None)。
    - 无记录 -> (None, None)
    - 只记账号 -> (username, None)
    - 同时记密码 -> (username, password)
    """
    try:
        if not os.path.exists(REMEMBER_FILE):
            return None, None
        with open(REMEMBER_FILE, "r", encoding="utf-8") as f:
            data = json.load(f)
        uname = data.get("username") or None
        if not data.get("has_pwd"):
            return uname, None
        key = _remember_key()
        nonce = base64.b64decode(data["nonce"])
        token = base64.b64decode(data["cipher"])
        cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
        pwd = cipher.decrypt(token).decode("utf-8")
        return uname, pwd
    except Exception:
        return None, None


def clear_remembered():
    """清除记住的账号密码"""
    try:
        if os.path.exists(REMEMBER_FILE):
            os.remove(REMEMBER_FILE)
    except Exception:
        pass


class DatabaseEncryption:
    """数据库加密管理器(优化版)"""

    @staticmethod
    def derive_key(master_password: str, salt: bytes = None) -> tuple:
        """从主密码派生加密密钥"""
        if salt is None:
            salt = get_random_bytes(16)

        # 使用PBKDF2派生密钥,增加迭代次数提高安全性
        key = PBKDF2(master_password, salt, 32, count=1000000)  # 增加到100万次迭代
        return key, salt

    @staticmethod
    def encrypt_data(data: str, key: bytes) -> str:
        """加密数据"""
        # 对数据进行UTF-8编码前的验证
        try:
            data.encode('utf-8')
        except UnicodeEncodeError:
            raise ValueError("数据包含无效的UTF-8字符")

        cipher = AES.new(key, AES.MODE_GCM)
        ciphertext, tag = cipher.encrypt_and_digest(data.encode('utf-8'))
        encrypted_data = base64.b64encode(cipher.nonce + tag + ciphertext).decode('utf-8')
        return encrypted_data

    @staticmethod
    def decrypt_data(encrypted_data: str, key: bytes) -> str:
        """解密数据"""
        try:
            data = base64.b64decode(encrypted_data.encode('utf-8'))
            if len(data) < 32:  # nonce(16) + tag(16) 至少需要32字节
                raise ValueError("加密数据长度不足")

            nonce = data[:16]
            tag = data[16:32]
            ciphertext = data[32:]

            cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
            plaintext = cipher.decrypt_and_verify(ciphertext, tag)
            return plaintext.decode('utf-8')
        except Exception as e:
            raise ValueError(f"解密失败: {str(e)}")


class ChineseStyle:
    """中国风样式配置"""

    @staticmethod
    def setup_style(app):
        """设置应用程序的中国风样式"""
        # 设置中文字体
        font = QFont("Microsoft YaHei", 10)
        app.setFont(font)

        # 设置中国风颜色方案
        app.setStyle("Fusion")
        palette = QPalette()
        palette.setColor(QPalette.Window, QColor(250, 245, 235))  # 米白色背景
        palette.setColor(QPalette.WindowText, QColor(101, 67, 33))  # 深棕色文字
        palette.setColor(QPalette.Base, QColor(255, 253, 248))  # 浅米色基础
        palette.setColor(QPalette.AlternateBase, QColor(245, 240, 230))
        palette.setColor(QPalette.ToolTipBase, QColor(255, 253, 248))
        palette.setColor(QPalette.ToolTipText, QColor(101, 67, 33))
        palette.setColor(QPalette.Text, QColor(101, 67, 33))
        palette.setColor(QPalette.Button, QColor(180, 150, 100))  # 古铜色按钮
        palette.setColor(QPalette.ButtonText, QColor(250, 245, 235))
        palette.setColor(QPalette.BrightText, Qt.red)
        palette.setColor(QPalette.Link, QColor(166, 77, 55))  # 朱红色链接
        palette.setColor(QPalette.Highlight, QColor(166, 77, 55))
        palette.setColor(QPalette.HighlightedText, Qt.white)
        app.setPalette(palette)

    @staticmethod
    def create_gradient_button(text, width=120, height=40, color1="#A64D37", color2="#C46C4E"):
        """创建中国风渐变按钮"""
        button = QPushButton(text)
        button.setFixedSize(width, height)
        button.setProperty("chineseStyle", True)
        button.setStyleSheet(f"""
            QPushButton[chineseStyle="true"] {{
                background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
                                            stop:0 {color1}, stop:1 {color2});
                color: white;
                border-radius: 20px;
                font-weight: bold;
                font-family: "Microsoft YaHei";
                font-size: 14px;
            }}
            QPushButton[chineseStyle="true"]:hover {{
                background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
                                            stop:0 #B45D47, stop:1 #D47C5E);
            }}
            QPushButton[chineseStyle="true"]:pressed {{
                background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
                                            stop:0 #95452F, stop:1 #B45D47);
            }}
        """)
        return button


class SingleApplication(QApplication):
    """单实例应用程序类"""

    def __init__(self, argv):
        super().__init__(argv)
        # 创建一个唯一的共享内存键(包含可执行文件路径避免冲突)
        app_key = f"墨香密码管理器_{hashlib.md5(sys.executable.encode()).hexdigest()[:8]}"
        self._shared_memory = QSharedMemory(app_key)

        # 尝试附加到共享内存,如果成功说明已有实例运行
        if self._shared_memory.attach():
            self._is_running = True
        else:
            # 创建共享内存
            self._is_running = False
            if not self._shared_memory.create(1):
                self._is_running = True

    def is_running(self):
        return self._is_running


class DatabaseManager:
    """数据库管理器"""

    def __init__(self, master_password=None):
        self.db_file = os.path.join(BASE_DIR, "password_manager.db")
        self.master_password = master_password
        self.encryption_key = None
        self.salt = None
        self.init_database()

    def get_connection(self):
        """获取数据库连接"""
        conn = sqlite3.connect(self.db_file)
        return conn

    def init_database(self):
        """初始化数据库表结构"""
        conn = self.get_connection()
        cursor = conn.cursor()

        # 创建加密配置表
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS encryption_config (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                salt TEXT NOT NULL,
                created_date TEXT NOT NULL
            )
        ''')

        # 创建用户表
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS users (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                username TEXT UNIQUE NOT NULL,
                master_password TEXT NOT NULL,
                created_date TEXT NOT NULL,
                last_login TEXT
            )
        ''')

        # 创建单位表
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS unit_table (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                user_id INTEGER NOT NULL,
                website_name TEXT NOT NULL,
                website_url TEXT,
                account TEXT NOT NULL,
                password TEXT NOT NULL,
                notes TEXT,
                created_time TEXT NOT NULL,
                updated_time TEXT NOT NULL,
                FOREIGN KEY (user_id) REFERENCES users (id)
            )
        ''')

        # 创建个人表
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS personal_table (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                user_id INTEGER NOT NULL,
                website_name TEXT NOT NULL,
                website_url TEXT,
                account TEXT NOT NULL,
                password TEXT NOT NULL,
                notes TEXT,
                created_time TEXT NOT NULL,
                updated_time TEXT NOT NULL,
                FOREIGN KEY (user_id) REFERENCES users (id)
            )
        ''')

        # 创建备份记录表
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS backup_records (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                user_id INTEGER NOT NULL,
                backup_file TEXT NOT NULL,
                backup_time TEXT NOT NULL,
                backup_type TEXT NOT NULL,
                record_count INTEGER,
                FOREIGN KEY (user_id) REFERENCES users (id)
            )
        ''')

        # 检查是否已存在加密配置
        cursor.execute('SELECT salt FROM encryption_config LIMIT 1')
        result = cursor.fetchone()

        if not result and self.master_password:
            # 初始化加密配置
            salt = get_random_bytes(16)
            key, _ = DatabaseEncryption.derive_key(self.master_password, salt)
            self.encryption_key = key
            self.salt = salt

            cursor.execute('''
                INSERT INTO encryption_config (salt, created_date)
                VALUES (?, ?)
            ''', (base64.b64encode(salt).decode('utf-8'),
                  datetime.now().strftime("%Y-%m-%d %H:%M:%S")))

        elif result and self.master_password:
            # 加载现有加密配置
            salt = base64.b64decode(result[0])
            key, _ = DatabaseEncryption.derive_key(self.master_password, salt)
            self.encryption_key = key
            self.salt = salt

        conn.commit()
        conn.close()

    def encrypt_password(self, password: str) -> str:
        """加密密码"""
        if self.encryption_key:
            return DatabaseEncryption.encrypt_data(password, self.encryption_key)
        return password

    def decrypt_password(self, encrypted_password: str) -> str:
        """解密密码"""
        if self.encryption_key:
            try:
                return DatabaseEncryption.decrypt_data(encrypted_password, self.encryption_key)
            except ValueError:
                # 如果解密失败,返回原始数据(可能是未加密的旧数据)
                return encrypted_password
        return encrypted_password


class PasswordManager:
    """密码管理器核心逻辑"""

    def __init__(self):
        self.db_manager = None
        self.backup_dir = os.path.join(BASE_DIR, "backups")
        self.current_user = None
        self.current_user_id = None
        self.current_data = {
            "tables": {
                "单位表": [],
                "个人表": []
            },
            "next_id": 1
        }

        # 创建备份目录
        if not os.path.exists(self.backup_dir):
            os.makedirs(self.backup_dir)

    def get_connection(self):
        """获取数据库连接"""
        return self.db_manager.get_connection() if self.db_manager else None

    def hash_password(self, password):
        """对密码进行哈希处理"""
        return hashlib.sha256(password.encode()).hexdigest()

    def register_user(self, username, password):
        """注册新用户"""
        if len(password) < 6:
            return False, "密码长度至少6位"

        hashed_password = self.hash_password(password)
        created_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

        try:
            # 初始化数据库管理器(首次注册时创建加密配置)
            self.db_manager = DatabaseManager(password)
            conn = self.get_connection()
            cursor = conn.cursor()

            cursor.execute('''
                INSERT INTO users (username, master_password, created_date, last_login)
                VALUES (?, ?, ?, ?)
            ''', (username, hashed_password, created_date, created_date))

            conn.commit()
            conn.close()
            return True, "注册成功"
        except sqlite3.IntegrityError:
            return False, "用户名已存在"
        except Exception as e:
            return False, f"注册失败: {str(e)}"

    def login(self, username, password):
        """用户登录"""
        try:
            # 先尝试不加密连接获取用户信息
            temp_db = DatabaseManager()
            conn = temp_db.get_connection()
            cursor = conn.cursor()

            cursor.execute('''
                SELECT id, master_password FROM users WHERE username = ?
            ''', (username,))

            result = cursor.fetchone()
            conn.close()

            if not result:
                return False, "用户不存在"

            user_id, stored_password = result

            if stored_password == self.hash_password(password):
                # 密码验证成功,使用主密码初始化数据库管理器
                self.db_manager = DatabaseManager(password)
                self.current_user = username
                self.current_user_id = user_id

                # 更新最后登录时间
                conn = self.get_connection()
                cursor = conn.cursor()
                cursor.execute('''
                    UPDATE users SET last_login = ? WHERE id = ?
                ''', (datetime.now().strftime("%Y-%m-%d %H:%M:%S"), user_id))
                conn.commit()
                conn.close()

                # 加载用户数据到内存
                self.load_user_data()
                return True, "登录成功"
            else:
                return False, "密码错误"

        except Exception as e:
            return False, f"登录失败: {str(e)}"

    def load_user_data(self):
        """加载用户数据到内存"""
        if not self.current_user_id:
            return

        try:
            conn = self.get_connection()
            cursor = conn.cursor()

            # 加载单位表数据
            cursor.execute('''
                SELECT id, website_name, website_url, account, password, notes
                FROM unit_table WHERE user_id = ? ORDER BY id
            ''', (self.current_user_id,))

            unit_records = []
            for row in cursor.fetchall():
                # 解密密码
                decrypted_password = self.db_manager.decrypt_password(row[4])
                unit_records.append({
                    "id": row[0],
                    "网站名称": row[1],
                    "网址": row[2] or "",
                    "账号": row[3],
                    "密码": decrypted_password,
                    "备注": row[5] or ""
                })

            # 加载个人表数据
            cursor.execute('''
                SELECT id, website_name, website_url, account, password, notes
                FROM personal_table WHERE user_id = ? ORDER BY id
            ''', (self.current_user_id,))

            personal_records = []
            for row in cursor.fetchall():
                # 解密密码
                decrypted_password = self.db_manager.decrypt_password(row[4])
                personal_records.append({
                    "id": row[0],
                    "网站名称": row[1],
                    "网址": row[2] or "",
                    "账号": row[3],
                    "密码": decrypted_password,
                    "备注": row[5] or ""
                })

            conn.close()

            # 更新内存中的数据
            self.current_data["tables"]["单位表"] = unit_records
            self.current_data["tables"]["个人表"] = personal_records

            # 计算下一个ID
            all_records = unit_records + personal_records
            if all_records:
                max_id = max(record["id"] for record in all_records)
                self.current_data["next_id"] = max_id + 1
            else:
                self.current_data["next_id"] = 1

        except Exception as e:
            print(f"加载用户数据失败: {str(e)}")

    def logout(self):
        """用户注销"""
        if self.current_user:
            self.current_user = None
            self.current_user_id = None
            self.db_manager = None
            self.current_data = {
                "tables": {
                    "单位表": [],
                    "个人表": []
                },
                "next_id": 1
            }
            return True
        return False

    def change_password(self, current_password, new_password):
        """修改用户密码(完整优化版)"""
        if not self.current_user_id:
            return False, "用户未登录"

        # 验证密码强度
        strength_valid, strength_msg = self.validate_password_strength(new_password)
        if not strength_valid:
            return False, strength_msg

        # 检查新密码是否与当前密码相同
        if current_password == new_password:
            return False, "新密码不能与当前密码相同"

        try:
            # 获取数据库连接
            conn = self.get_connection()
            cursor = conn.cursor()

            # 验证当前密码
            cursor.execute('SELECT master_password FROM users WHERE id = ?', (self.current_user_id,))
            result = cursor.fetchone()

            if not result or result[0] != self.hash_password(current_password):
                conn.close()
                return False, "当前密码错误"

            # 创建新的数据库管理器实例
            new_db_manager = DatabaseManager(new_password)

            # 备份当前数据库管理器
            old_db_manager = self.db_manager

            try:
                # 开始事务
                conn.execute('BEGIN TRANSACTION')

                # 重新加密所有密码记录
                self._reencrypt_all_passwords_in_transaction(old_db_manager, new_db_manager, cursor)

                # 更新用户主密码哈希
                cursor.execute('UPDATE users SET master_password = ? WHERE id = ?',
                               (self.hash_password(new_password), self.current_user_id))

                # 提交事务
                conn.commit()

                # 更新当前数据库管理器
                self.db_manager = new_db_manager

                # 重新加载用户数据以确保一致性
                self.load_user_data()

                return True, "密码修改成功"

            except Exception as e:
                # 回滚事务
                conn.rollback()
                # 恢复原来的数据库管理器
                self.db_manager = old_db_manager
                return False, f"密码修改失败: {str(e)}"
            finally:
                conn.close()

        except Exception as e:
            return False, f"密码修改失败: {str(e)}"

    def _reencrypt_all_passwords_in_transaction(self, old_db_manager, new_db_manager, cursor):
        """在事务中重新加密所有密码记录"""
        # 重新加密单位表
        cursor.execute('SELECT id, password FROM unit_table WHERE user_id = ?', (self.current_user_id,))
        unit_records = cursor.fetchall()

        for record_id, old_encrypted_password in unit_records:
            try:
                # 使用旧密钥解密
                decrypted_password = old_db_manager.decrypt_password(old_encrypted_password)
                # 使用新密钥加密
                new_encrypted_password = new_db_manager.encrypt_password(decrypted_password)

                # 更新数据库记录
                cursor.execute('UPDATE unit_table SET password = ? WHERE id = ?',
                               (new_encrypted_password, record_id))

            except Exception as e:
                raise Exception(f"重新加密单位表记录 {record_id} 失败: {str(e)}")

        # 重新加密个人表
        cursor.execute('SELECT id, password FROM personal_table WHERE user_id = ?', (self.current_user_id,))
        personal_records = cursor.fetchall()

        for record_id, old_encrypted_password in personal_records:
            try:
                # 使用旧密钥解密
                decrypted_password = old_db_manager.decrypt_password(old_encrypted_password)
                # 使用新密钥加密
                new_encrypted_password = new_db_manager.encrypt_password(decrypted_password)

                # 更新数据库记录
                cursor.execute('UPDATE personal_table SET password = ? WHERE id = ?',
                               (new_encrypted_password, record_id))

            except Exception as e:
                raise Exception(f"重新加密个人表记录 {record_id} 失败: {str(e)}")

    def validate_password_strength(self, password):
        """验证密码强度"""
        if len(password) < 8:
            return False, "密码长度至少8位"

        if len(password) > 128:
            return False, "密码长度不能超过128位"

        # 检查字符类型
        has_upper = any(c.isupper() for c in password)
        has_lower = any(c.islower() for c in password)
        has_digit = any(c.isdigit() for c in password)

        if not (has_upper and has_lower and has_digit):
            return False, "密码必须包含大写字母、小写字母和数字"

        # 检查常见弱密码
        weak_passwords = [
            'password', '123456', 'qwerty', 'admin', 'welcome',
            'password123', '12345678', '123456789', '123123',
            str(self.current_user) if self.current_user else ''
        ]

        if password.lower() in weak_passwords:
            return False, "密码过于简单,请使用更复杂的密码"

        return True, "密码强度符合要求"

    def backup_data(self, backup_type="manual"):
        """备份数据到SQLite数据库"""
        if not self.current_user_id:
            return False, "用户未登录"

        try:
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            if backup_type == "auto":
                backup_file = os.path.join(self.backup_dir, f"{self.current_user}_auto_{timestamp}.db")
            else:
                backup_file = os.path.join(self.backup_dir, f"{self.current_user}_manual_{timestamp}.db")

            # 创建备份数据库
            backup_conn = sqlite3.connect(backup_file)
            backup_cursor = backup_conn.cursor()

            # 复制表结构
            backup_cursor.execute('''
                CREATE TABLE IF NOT EXISTS unit_table_backup (
                    id INTEGER PRIMARY KEY,
                    user_id INTEGER,
                    website_name TEXT,
                    website_url TEXT,
                    account TEXT,
                    password TEXT,
                    notes TEXT,
                    created_time TEXT,
                    updated_time TEXT
                )
            ''')

            backup_cursor.execute('''
                CREATE TABLE IF NOT EXISTS personal_table_backup (
                    id INTEGER PRIMARY KEY,
                    user_id INTEGER,
                    website_name TEXT,
                    website_url TEXT,
                    account TEXT,
                    password TEXT,
                    notes TEXT,
                    created_time TEXT,
                    updated_time TEXT
                )
            ''')

            # 复制数据
            conn = self.get_connection()
            cursor = conn.cursor()

            # 备份单位表
            cursor.execute('SELECT * FROM unit_table WHERE user_id = ?', (self.current_user_id,))
            for row in cursor.fetchall():
                backup_cursor.execute('''
                    INSERT INTO unit_table_backup VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
                ''', row)

            # 备份个人表
            cursor.execute('SELECT * FROM personal_table WHERE user_id = ?', (self.current_user_id,))
            for row in cursor.fetchall():
                backup_cursor.execute('''
                    INSERT INTO personal_table_backup VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
                ''', row)

            conn.close()

            # 记录备份信息
            record_count = len(self.current_data["tables"]["单位表"]) + len(self.current_data["tables"]["个人表"])
            backup_cursor.execute('''
                CREATE TABLE IF NOT EXISTS backup_info (
                    backup_time TEXT,
                    user_name TEXT,
                    record_count INTEGER,
                    backup_type TEXT
                )
            ''')
            backup_cursor.execute('''
                INSERT INTO backup_info VALUES (?, ?, ?, ?)
            ''', (datetime.now().strftime("%Y-%m-%d %H:%M:%S"), self.current_user, record_count, backup_type))

            backup_conn.commit()
            backup_conn.close()

            return True, f"备份成功: {os.path.basename(backup_file)}"
        except Exception as e:
            return False, f"备份失败: {str(e)}"

    # 其他方法保持不变...
    def verify_master_password(self, password):
        """验证主密码是否正确"""
        if not self.current_user_id:
            return False

        try:
            conn = self.get_connection()
            cursor = conn.cursor()
            cursor.execute('''
                SELECT master_password FROM users WHERE id = ?
            ''', (self.current_user_id,))

            result = cursor.fetchone()
            conn.close()

            return result and result[0] == self.hash_password(password)
        except Exception as e:
            return False

    def add_record(self, table_name, record_data):
        """添加新记录"""
        if not self.current_user_id:
            return False

        try:
            conn = self.get_connection()
            cursor = conn.cursor()

            current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

            # 加密密码
            encrypted_password = self.db_manager.encrypt_password(record_data["密码"])

            if table_name == "单位表":
                cursor.execute('''
                    INSERT INTO unit_table 
                    (user_id, website_name, website_url, account, password, notes, created_time, updated_time)
                    VALUES (?, ?, ?, ?, ?, ?, ?, ?)
                ''', (
                    self.current_user_id,
                    record_data["网站名称"],
                    record_data["网址"],
                    record_data["账号"],
                    encrypted_password,
                    record_data["备注"],
                    current_time,
                    current_time
                ))
            else:  # 个人表
                cursor.execute('''
                    INSERT INTO personal_table 
                    (user_id, website_name, website_url, account, password, notes, created_time, updated_time)
                    VALUES (?, ?, ?, ?, ?, ?, ?, ?)
                ''', (
                    self.current_user_id,
                    record_data["网站名称"],
                    record_data["网址"],
                    record_data["账号"],
                    encrypted_password,
                    record_data["备注"],
                    current_time,
                    current_time
                ))

            conn.commit()
            record_id = cursor.lastrowid
            conn.close()

            # 更新内存数据
            record_data["id"] = record_id
            self.current_data["tables"][table_name].append(record_data)
            self.current_data["next_id"] = max(self.current_data["next_id"], record_id + 1)

            return True
        except Exception as e:
            print(f"添加记录失败: {str(e)}")
            return False

    def update_record(self, table_name, record_id, new_data):
        """更新记录"""
        if not self.current_user_id:
            return False

        try:
            conn = self.get_connection()
            cursor = conn.cursor()

            current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

            # 加密密码
            encrypted_password = self.db_manager.encrypt_password(new_data["密码"])

            if table_name == "单位表":
                cursor.execute('''
                    UPDATE unit_table SET 
                    website_name = ?, website_url = ?, account = ?, password = ?, notes = ?, updated_time = ?
                    WHERE id = ? AND user_id = ?
                ''', (
                    new_data["网站名称"],
                    new_data["网址"],
                    new_data["账号"],
                    encrypted_password,
                    new_data["备注"],
                    current_time,
                    record_id,
                    self.current_user_id
                ))
            else:  # 个人表
                cursor.execute('''
                    UPDATE personal_table SET 
                    website_name = ?, website_url = ?, account = ?, password = ?, notes = ?, updated_time = ?
                    WHERE id = ? AND user_id = ?
                ''', (
                    new_data["网站名称"],
                    new_data["网址"],
                    new_data["账号"],
                    encrypted_password,
                    new_data["备注"],
                    current_time,
                    record_id,
                    self.current_user_id
                ))

            conn.commit()
            conn.close()

            # 更新内存数据
            for record in self.current_data["tables"][table_name]:
                if record["id"] == record_id:
                    record.update(new_data)
                    break

            return True
        except Exception as e:
            print(f"更新记录失败: {str(e)}")
            return False

    def delete_record(self, table_name, record_id):
        """删除记录"""
        if not self.current_user_id:
            return False

        try:
            conn = self.get_connection()
            cursor = conn.cursor()

            if table_name == "单位表":
                cursor.execute('''
                    DELETE FROM unit_table WHERE id = ? AND user_id = ?
                ''', (record_id, self.current_user_id))
            else:  # 个人表
                cursor.execute('''
                    DELETE FROM personal_table WHERE id = ? AND user_id = ?
                ''', (record_id, self.current_user_id))

            conn.commit()
            conn.close()

            # 更新内存数据
            self.current_data["tables"][table_name] = [
                record for record in self.current_data["tables"][table_name]
                if record["id"] != record_id
            ]

            return True
        except Exception as e:
            print(f"删除记录失败: {str(e)}")
            return False

    def search_records(self, table_name, search_term, search_field="所有字段"):
        """搜索记录"""
        if not search_term:
            return self.current_data["tables"][table_name]

        results = []
        for record in self.current_data["tables"][table_name]:
            if search_field != "所有字段":
                field_value = record.get(search_field, "")
                if search_term.lower() in str(field_value).lower():
                    results.append(record)
            else:
                for value in record.values():
                    if search_term.lower() in str(value).lower():
                        results.append(record)
                        break
        return results

    def import_from_excel(self, file_path, target_table, sheet_name=None):
        """从Excel文件导入数据到指定表"""
        if not self.current_user_id:
            return False, "用户未登录"

        if target_table not in ["单位表", "个人表"]:
            return False, "目标表必须是'单位表'或'个人表'"

        try:
            df = _read_import_dataframe(file_path, sheet_name)

            if df.empty:
                return False, "文件为空或没有数据"

            field_mapping = {
                '网站名称': ['网站名称', '网站名', '网站', '名称', '站点名称', '平台名称'],
                '网址': ['网址', '网站地址', 'URL', '链接', '地址', '网站链接'],
                '账号': ['账号', '账户', '用户名', '用户', '登录名', '用户账号'],
                '密码': ['密码', '登陆密码', '登录密码', 'pass', 'passwd'],
                '备注': ['备注', '说明', '注释', '描述', 'note', 'description']
            }

            actual_mapping = {}
            for target_field, possible_names in field_mapping.items():
                for col in df.columns:
                    if any(name in str(col) for name in possible_names):
                        actual_mapping[target_field] = col
                        break
                else:
                    actual_mapping[target_field] = None

            imported_count = 0
            skipped_count = 0
            error_count = 0

            for index, row in df.iterrows():
                try:
                    website_name = row[actual_mapping['网站名称']] if actual_mapping['网站名称'] is not None else ""
                    website_url = row[actual_mapping['网址']] if actual_mapping['网址'] is not None else ""
                    account = row[actual_mapping['账号']] if actual_mapping['账号'] is not None else ""
                    password = row[actual_mapping['密码']] if actual_mapping['密码'] is not None else ""
                    notes = row[actual_mapping['备注']] if actual_mapping['备注'] is not None else ""

                    website_name = str(website_name).strip() if pd.notna(website_name) else ""
                    website_url = str(website_url).strip() if pd.notna(website_url) else ""
                    account = str(account).strip() if pd.notna(account) else ""
                    password = str(password).strip() if pd.notna(password) else ""
                    notes = str(notes).strip() if pd.notna(notes) else ""

                    if not website_name and not account:
                        skipped_count += 1
                        continue

                    record_data = {
                        "网站名称": website_name,
                        "网址": website_url,
                        "账号": account,
                        "密码": password,
                        "备注": notes
                    }

                    if self.add_record(target_table, record_data):
                        imported_count += 1
                    else:
                        error_count += 1
                        skipped_count += 1

                except Exception as e:
                    error_count += 1
                    skipped_count += 1
                    continue

            return True, f"导入完成:成功导入 {imported_count} 条记录,跳过 {skipped_count} 条记录,错误 {error_count} 条记录"

        except Exception as e:
            return False, f"导入失败: {str(e)}"


class SecurityDialog(QDialog):
    """安全验证对话框"""

    def __init__(self, parent=None, title="安全验证", message="请输入您的主密码以继续操作"):
        super().__init__(parent)
        self.setWindowTitle(title)
        self.setFixedSize(350, 200)
        self.setModal(True)

        layout = QVBoxLayout(self)
        layout.setSpacing(15)

        title_label = QLabel(title)
        title_label.setAlignment(Qt.AlignCenter)
        title_label.setStyleSheet("""
            QLabel {
                font-size: 18px;
                font-weight: bold;
                color: #654321;
                font-family: "Microsoft YaHei";
                padding: 10px;
            }
        """)
        layout.addWidget(title_label)

        message_label = QLabel(message)
        message_label.setAlignment(Qt.AlignCenter)
        message_label.setStyleSheet("font-family: 'Microsoft YaHei'; color: #8C7853;")
        message_label.setWordWrap(True)
        layout.addWidget(message_label)

        password_layout = QHBoxLayout()
        password_label = QLabel("主密码:")
        password_label.setFixedWidth(80)
        password_label.setStyleSheet("font-family: 'Microsoft YaHei'; color: #654321;")
        self.password_input = QLineEdit()
        self.password_input.setEchoMode(QLineEdit.Password)
        self.password_input.setStyleSheet("""
            QLineEdit {
                border: 1px solid #D9C7A7;
                border-radius: 5px;
                padding: 5px;
                font-family: "Microsoft YaHei";
            }
        """)
        password_layout.addWidget(password_label)
        password_layout.addWidget(self.password_input)
        layout.addLayout(password_layout)

        button_layout = QHBoxLayout()
        button_layout.addStretch()

        ok_btn = ChineseStyle.create_gradient_button("验证", 80, 35)
        ok_btn.clicked.connect(self.accept)

        cancel_btn = ChineseStyle.create_gradient_button("取消", 80, 35, "#8C7853", "#A8967A")
        cancel_btn.clicked.connect(self.reject)

        button_layout.addWidget(ok_btn)
        button_layout.addWidget(cancel_btn)
        layout.addLayout(button_layout)

        self.password_input.returnPressed.connect(self.accept)

    def get_password(self):
        return self.password_input.text()


class ChangePasswordDialog(QDialog):
    """修改密码对话框"""

    def __init__(self, parent=None, password_manager=None):
        super().__init__(parent)
        self.pm = password_manager
        self.init_ui()

    def init_ui(self):
        self.setWindowTitle("修改密码")
        self.setFixedSize(500, 450)
        self.setModal(True)

        layout = QVBoxLayout(self)
        layout.setSpacing(20)

        # 标题
        title_label = QLabel("修改主密码")
        title_label.setAlignment(Qt.AlignCenter)
        title_label.setStyleSheet("""
            QLabel {
                font-size: 18px;
                font-weight: bold;
                color: #654321;
                font-family: "Microsoft YaHei";
                padding: 10px;
            }
        """)
        layout.addWidget(title_label)

        # 当前密码
        current_pwd_layout = QHBoxLayout()
        current_pwd_label = QLabel("当前密码:")
        current_pwd_label.setFixedWidth(100)
        current_pwd_label.setStyleSheet("font-family: 'Microsoft YaHei'; color: #654321;")
        self.current_pwd_input = QLineEdit()
        self.current_pwd_input.setEchoMode(QLineEdit.Password)
        self.current_pwd_input.setStyleSheet("""
            QLineEdit {
                border: 1px solid #D9C7A7;
                border-radius: 5px;
                padding: 5px;
                font-family: "Microsoft YaHei";
            }
        """)
        current_pwd_layout.addWidget(current_pwd_label)
        current_pwd_layout.addWidget(self.current_pwd_input)
        layout.addLayout(current_pwd_layout)

        # 新密码
        new_pwd_layout = QHBoxLayout()
        new_pwd_label = QLabel("新密码:")
        new_pwd_label.setFixedWidth(100)
        new_pwd_label.setStyleSheet("font-family: 'Microsoft YaHei'; color: #654321;")
        self.new_pwd_input = QLineEdit()
        self.new_pwd_input.setEchoMode(QLineEdit.Password)
        self.new_pwd_input.textChanged.connect(self.validate_password_strength)
        self.new_pwd_input.setStyleSheet(self.current_pwd_input.styleSheet())
        new_pwd_layout.addWidget(new_pwd_label)
        new_pwd_layout.addWidget(self.new_pwd_input)
        layout.addLayout(new_pwd_layout)

        # 密码强度指示器
        self.strength_label = QLabel("")
        self.strength_label.setStyleSheet("font-family: 'Microsoft YaHei'; font-size: 12px;")
        layout.addWidget(self.strength_label)

        # 确认新密码
        confirm_pwd_layout = QHBoxLayout()
        confirm_pwd_label = QLabel("确认新密码:")
        confirm_pwd_label.setFixedWidth(100)
        confirm_pwd_label.setStyleSheet("font-family: 'Microsoft YaHei'; color: #654321;")
        self.confirm_pwd_input = QLineEdit()
        self.confirm_pwd_input.setEchoMode(QLineEdit.Password)
        self.confirm_pwd_input.textChanged.connect(self.check_password_match)
        self.confirm_pwd_input.setStyleSheet(self.current_pwd_input.styleSheet())
        confirm_pwd_layout.addWidget(confirm_pwd_label)
        confirm_pwd_layout.addWidget(self.confirm_pwd_input)
        layout.addLayout(confirm_pwd_layout)

        # 密码匹配指示器
        self.match_label = QLabel("")
        self.match_label.setStyleSheet("font-family: 'Microsoft YaHei'; font-size: 12px;")
        layout.addWidget(self.match_label)

        # 密码要求说明
        requirements_label = QLabel("""
        <span style='font-family: "Microsoft YaHei"; font-size: 11px; color: #8C7853;'>
        &#8226; 密码长度至少8位<br>
        &#8226; 包含大写字母、小写字母和数字<br>
        &#8226; 建议使用特殊字符增强安全性
        </span>
        """)
        requirements_label.setWordWrap(True)
        layout.addWidget(requirements_label)

        # 按钮布局
        button_layout = QHBoxLayout()
        button_layout.addStretch()

        self.change_btn = ChineseStyle.create_gradient_button("确认修改", 100, 35)
        self.change_btn.clicked.connect(self.change_password)
        self.change_btn.setEnabled(False)  # 初始禁用

        cancel_btn = ChineseStyle.create_gradient_button("取消", 100, 35, "#8C7853", "#A8967A")
        cancel_btn.clicked.connect(self.reject)

        button_layout.addWidget(self.change_btn)
        button_layout.addWidget(cancel_btn)
        layout.addLayout(button_layout)

        # 连接回车键
        self.confirm_pwd_input.returnPressed.connect(self.change_password)

    def validate_password_strength(self):
        """验证密码强度"""
        password = self.new_pwd_input.text()

        if len(password) < 8:
            self.strength_label.setText("&#10060; 密码长度至少8位")
            self.strength_label.setStyleSheet("color: #FF6B6B; font-family: 'Microsoft YaHei';")
            return False

        has_upper = any(c.isupper() for c in password)
        has_lower = any(c.islower() for c in password)
        has_digit = any(c.isdigit() for c in password)

        if not (has_upper and has_lower and has_digit):
            self.strength_label.setText("&#10060; 需包含大小写字母和数字")
            self.strength_label.setStyleSheet("color: #FF6B6B; font-family: 'Microsoft YaHei';")
            return False

        # 评估密码强度
        strength = 0
        if len(password) >= 12:
            strength += 1
        if any(c in '!@#$%^&*()_+-=[]{}|;:,.<>?/' for c in password):
            strength += 1

        if strength >= 1:
            self.strength_label.setText("&#9989; 密码强度:强")
            self.strength_label.setStyleSheet("color: #51CF66; font-family: 'Microsoft YaHei';")
        else:
            self.strength_label.setText("&#9888;&#65039; 密码强度:中")
            self.strength_label.setStyleSheet("color: #FCC419; font-family: 'Microsoft YaHei';")

        return True

    def check_password_match(self):
        """检查密码是否匹配"""
        new_pwd = self.new_pwd_input.text()
        confirm_pwd = self.confirm_pwd_input.text()

        if not new_pwd:
            self.match_label.setText("")
            self.change_btn.setEnabled(False)
            return False

        if new_pwd == confirm_pwd:
            self.match_label.setText("&#9989; 密码匹配")
            self.match_label.setStyleSheet("color: #51CF66; font-family: 'Microsoft YaHei';")
            # 只有在密码强度合格且匹配时才启用按钮
            if self.validate_password_strength():
                self.change_btn.setEnabled(True)
            return True
        else:
            self.match_label.setText("&#10060; 密码不匹配")
            self.match_label.setStyleSheet("color: #FF6B6B; font-family: 'Microsoft YaHei';")
            self.change_btn.setEnabled(False)
            return False

    def change_password(self):
        """执行密码修改"""
        current_password = self.current_pwd_input.text().strip()
        new_password = self.new_pwd_input.text().strip()
        confirm_password = self.confirm_pwd_input.text().strip()

        # 验证输入
        if not current_password:
            QMessageBox.warning(self, "输入错误", "请输入当前密码")
            self.current_pwd_input.setFocus()
            return

        if not new_password:
            QMessageBox.warning(self, "输入错误", "请输入新密码")
            self.new_pwd_input.setFocus()
            return

        if new_password != confirm_password:
            QMessageBox.warning(self, "输入错误", "新密码和确认密码不匹配")
            self.confirm_pwd_input.setFocus()
            return

        if not self.validate_password_strength():
            QMessageBox.warning(self, "密码强度不足", "请按照要求设置更强的密码")
            self.new_pwd_input.setFocus()
            return

        # 验证当前密码
        if not self.pm.verify_master_password(current_password):
            QMessageBox.warning(self, "验证失败", "当前密码错误")
            self.current_pwd_input.clear()
            self.current_pwd_input.setFocus()
            return

        # 检查新密码是否与旧密码相同
        if current_password == new_password:
            QMessageBox.warning(self, "密码重复", "新密码不能与当前密码相同")
            self.new_pwd_input.clear()
            self.confirm_pwd_input.clear()
            self.new_pwd_input.setFocus()
            return

        # 执行密码修改
        try:
            success, message = self.pm.change_password(current_password, new_password)
            if success:
                QMessageBox.information(self, "修改成功", "密码修改成功")
                self.accept()
            else:
                QMessageBox.critical(self, "修改失败", message)
        except Exception as e:
            QMessageBox.critical(self, "错误", f"修改密码时发生错误: {str(e)}")


class LoginWindow(QDialog):
    """登录窗口"""

    login_success = pyqtSignal(str)  # 登录成功信号

    def __init__(self, password_manager):
        super().__init__()
        self.pm = password_manager
        self.init_ui()

    def init_ui(self):
        self.setWindowTitle("墨香 - 登录")
        self.setFixedSize(420, 460)
        self.setWindowFlags(Qt.FramelessWindowHint)
        self.setAttribute(Qt.WA_TranslucentBackground)
        self.setWindowIcon(get_app_icon())

        self.main_widget = QWidget(self)
        self.main_widget.setObjectName("main_widget")
        self.main_widget.setStyleSheet("""
            #main_widget {
                background: qlineargradient(x1:0, y1:0, x2:1, y2:1,
                                            stop:0 #F8F4E9, stop:1 #F0E9D9);
                border-radius: 15px;
                border: 1px solid #D9C7A7;
            }
        """)

        layout = QVBoxLayout(self.main_widget)
        layout.setContentsMargins(30, 25, 30, 25)
        layout.setSpacing(15)

        title_layout = QVBoxLayout()
        title_layout.setAlignment(Qt.AlignCenter)

        deco_label = QLabel("&#10048;&#10048;&#10048;&#10048;")
        deco_label.setAlignment(Qt.AlignCenter)
        deco_label.setStyleSheet("font-size: 32px; color: #A64D37;")
        title_layout.addWidget(deco_label)

        title_label = QLabel("墨香账号平台")
        title_label.setAlignment(Qt.AlignCenter)
        title_label.setStyleSheet("""
            QLabel {
                font-size: 24px;
                font-weight: bold;
                color: #654321;
                font-family: "Microsoft YaHei";
                margin-bottom: 2px;
            }
        """)
        title_layout.addWidget(title_label)

        subtitle_label = QLabel("安全存储 · 优雅管理")
        subtitle_label.setAlignment(Qt.AlignCenter)
        subtitle_label.setStyleSheet("font-size: 14px; color: #8C7853;")
        title_layout.addWidget(subtitle_label)

        layout.addLayout(title_layout)

        form_layout = QVBoxLayout()
        form_layout.setSpacing(12)

        username_group = QGroupBox()
        username_group.setStyleSheet("""
            QGroupBox {
                border: 1px solid #D9C7A7;
                border-radius: 10px;
                padding: 6px 10px;
                background: rgba(255, 255, 255, 0.7);
            }
            QGroupBox::title {
                subcontrol-origin: margin;
                left: 10px;
                padding: 0 5px 0 5px;
                color: #8C7853;
            }
        """)
        username_group.setTitle("用户名")

        username_layout = QHBoxLayout(username_group)
        self.username_input = QLineEdit()
        self.username_input.setPlaceholderText("请输入用户名")
        self.username_input.setStyleSheet("""
            QLineEdit {
                border: none;
                background: transparent;
                font-size: 14px;
                color: #654321;
                min-height: 26px;
                max-height: 30px;
                padding: 2px 0px;
            }
        """)
        username_layout.addWidget(self.username_input)

        form_layout.addWidget(username_group)

        password_group = QGroupBox()
        password_group.setStyleSheet(username_group.styleSheet())
        password_group.setTitle("密码")

        password_layout = QHBoxLayout(password_group)
        self.password_input = QLineEdit()
        self.password_input.setPlaceholderText("请输入密码")
        self.password_input.setEchoMode(QLineEdit.Password)
        self.password_input.setStyleSheet(self.username_input.styleSheet())
        password_layout.addWidget(self.password_input)

        form_layout.addWidget(password_group)

        layout.addLayout(form_layout)

        # 记住账号 / 记住密码(两级:默认只记账号,密码需额外勾选)
        remember_layout = QHBoxLayout()
        remember_layout.setSpacing(20)
        self.remember_user_checkbox = QCheckBox("记住账号")
        self.remember_user_checkbox.setChecked(True)  # 默认勾选,只记账号
        self.remember_user_checkbox.setStyleSheet(
            "font-family: 'Microsoft YaHei'; color: #8C7853; spacing: 6px;")
        self.remember_pwd_checkbox = QCheckBox("记住密码")
        self.remember_pwd_checkbox.setStyleSheet(
            "font-family: 'Microsoft YaHei'; color: #8C7853; spacing: 6px;")
        self.remember_pwd_checkbox.setEnabled(False)  # 未勾选记账号时禁用
        remember_layout.addWidget(self.remember_user_checkbox, 0, Qt.AlignLeft)
        remember_layout.addWidget(self.remember_pwd_checkbox, 0, Qt.AlignLeft)
        remember_layout.addStretch()
        layout.addLayout(remember_layout)

        self.remember_user_checkbox.toggled.connect(self._on_remember_user_toggled)

        button_layout = QHBoxLayout()
        button_layout.setSpacing(20)

        self.login_btn = ChineseStyle.create_gradient_button("登录", 120, 40)
        self.login_btn.clicked.connect(self.login)

        self.register_btn = ChineseStyle.create_gradient_button("注册", 120, 40, "#8C7853", "#A8967A")
        self.register_btn.clicked.connect(self.register)

        button_layout.addWidget(self.login_btn)
        button_layout.addWidget(self.register_btn)

        layout.addLayout(button_layout)

        footer_label = QLabel("· 安全第一 · 隐私至上 ·")
        footer_label.setAlignment(Qt.AlignCenter)
        footer_label.setStyleSheet("font-size: 12px; color: #8C7853;")
        layout.addWidget(footer_label)

        main_layout = QVBoxLayout(self)
        main_layout.addWidget(self.main_widget)
        main_layout.setContentsMargins(15, 15, 15, 15)

        self.password_input.returnPressed.connect(self.login)
        self.center_window()

    def center_window(self):
        """窗口居中显示"""
        screen = QApplication.primaryScreen().availableGeometry()
        size = self.geometry()
        self.move((screen.width() - size.width()) // 2, (screen.height() - size.height()) // 2)

    def _on_remember_user_toggled(self, checked):
        """取消「记住账号」时,禁用并取消「记住密码」"""
        self.remember_pwd_checkbox.setEnabled(checked)
        if not checked:
            self.remember_pwd_checkbox.setChecked(False)

    def login(self):
        username = self.username_input.text().strip()
        password = self.password_input.text().strip()

        if not username or not password:
            QMessageBox.warning(self, "输入错误", "请输入用户名和密码")
            return

        success, message = self.pm.login(username, password)
        if success:
            if self.remember_user_checkbox.isChecked():
                save_remembered(
                    username,
                    remember_pwd=self.remember_pwd_checkbox.isChecked(),
                    master_password=password)
            else:
                clear_remembered()
            self.login_success.emit(username)
            self.hide()
        else:
            QMessageBox.critical(self, "登录失败", message)

    def register(self):
        username = self.username_input.text().strip()
        password = self.password_input.text().strip()

        if not username or not password:
            QMessageBox.warning(self, "输入错误", "请输入用户名和密码")
            return

        success, message = self.pm.register_user(username, password)
        if success:
            QMessageBox.information(self, "注册成功", "账号注册成功,请登录")
            self.password_input.clear()
        else:
            QMessageBox.critical(self, "注册失败", message)

    def show_login(self):
        """显示登录窗口;若已记住账号则自动填充(默认只记账号,密码需额外勾选)"""
        self.username_input.clear()
        self.password_input.clear()
        self.remember_user_checkbox.setChecked(True)  # 默认只记账号
        self.remember_pwd_checkbox.setChecked(False)
        uname, pwd = load_remembered()
        if uname:
            self.username_input.setText(uname)
            self.remember_user_checkbox.setChecked(True)
            if pwd:
                self.password_input.setText(pwd)
                self.remember_pwd_checkbox.setChecked(True)
        # 同步「记住密码」可用状态(依赖「记住账号」是否勾选)
        self.remember_pwd_checkbox.setEnabled(self.remember_user_checkbox.isChecked())
        self.show()
        self.raise_()
        self.activateWindow()


class PasswordGeneratorDialog(QDialog):
    """强密码生成器对话框(功能2)"""

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("密码生成器")
        self.setFixedSize(460, 330)
        self.setWindowIcon(get_app_icon())
        self.generated = ""
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout(self)
        layout.setSpacing(12)

        title = QLabel("&#128273; 强密码生成器")
        title.setStyleSheet("font-size: 18px; font-weight: bold; color: #654321; font-family: 'Microsoft YaHei';")
        title.setAlignment(Qt.AlignCenter)
        layout.addWidget(title)

        # 长度
        len_layout = QHBoxLayout()
        len_label = QLabel("长度:")
        len_label.setFixedWidth(50)
        len_label.setStyleSheet("font-family: 'Microsoft YaHei'; color: #654321;")
        self.len_spin = QSpinBox()
        self.len_spin.setRange(8, 64)
        self.len_spin.setValue(16)
        self.len_spin.setStyleSheet("font-family: 'Microsoft YaHei';")
        len_layout.addWidget(len_label)
        len_layout.addWidget(self.len_spin)
        len_layout.addStretch()
        layout.addLayout(len_layout)

        # 字符类型
        opt_label = QLabel("包含字符:")
        opt_label.setStyleSheet("font-family: 'Microsoft YaHei'; color: #654321; font-weight: bold;")
        layout.addWidget(opt_label)
        self.cb_upper = QCheckBox("大写字母 (A-Z)")
        self.cb_lower = QCheckBox("小写字母 (a-z)")
        self.cb_digit = QCheckBox("数字 (0-9)")
        self.cb_symbol = QCheckBox("符号 (!@#$...)")
        self.cb_upper.setChecked(True)
        self.cb_lower.setChecked(True)
        self.cb_digit.setChecked(True)
        self.cb_symbol.setChecked(True)
        for cb in (self.cb_upper, self.cb_lower, self.cb_digit, self.cb_symbol):
            cb.setStyleSheet("font-family: 'Microsoft YaHei';")
            layout.addWidget(cb)

        # 生成结果
        self.result_edit = QLineEdit()
        self.result_edit.setReadOnly(True)
        self.result_edit.setStyleSheet("""
            QLineEdit {
                border: 1px solid #D9C7A7;
                border-radius: 5px;
                padding: 6px;
                font-family: 'Consolas','Microsoft YaHei';
                font-size: 13px;
                color: #654321;
            }
        """)
        layout.addWidget(self.result_edit)

        # 按钮
        btn_layout = QHBoxLayout()
        btn_layout.addStretch()
        regen_btn = QPushButton("重新生成")
        regen_btn.setStyleSheet(self.cb_upper.styleSheet())
        regen_btn.clicked.connect(self.generate)
        copy_btn = QPushButton("复制")
        copy_btn.setStyleSheet(self.cb_upper.styleSheet())
        copy_btn.clicked.connect(self.copy_password)
        use_btn = ChineseStyle.create_gradient_button("使用此密码", 120, 32)
        use_btn.clicked.connect(self.accept)
        cancel_btn = ChineseStyle.create_gradient_button("取消", 100, 32, "#8C7853", "#A8967A")
        cancel_btn.clicked.connect(self.reject)
        btn_layout.addWidget(regen_btn)
        btn_layout.addWidget(copy_btn)
        btn_layout.addWidget(use_btn)
        btn_layout.addWidget(cancel_btn)
        layout.addLayout(btn_layout)

        self.generate()

    def generate(self):
        """使用密码学安全随机数生成密码"""
        length = self.len_spin.value()
        pool = ""
        if self.cb_upper.isChecked():
            pool += string.ascii_uppercase
        if self.cb_lower.isChecked():
            pool += string.ascii_lowercase
        if self.cb_digit.isChecked():
            pool += string.digits
        if self.cb_symbol.isChecked():
            pool += "!@#$%^&*()-_=+[]{};:,.<>?/"
        if not pool:
            pool = string.ascii_letters + string.digits
        self.generated = "".join(secrets.choice(pool) for _ in range(length))
        self.result_edit.setText(self.generated)

    def copy_password(self):
        cb = QApplication.clipboard()
        if cb:
            cb.setText(self.generated)
            QMessageBox.information(self, "已复制", "密码已复制到剪贴板")

    def get_password(self):
        return self.generated


class RecordDialog(QDialog):
    """记录编辑对话框"""

    def __init__(self, parent=None, title="", record=None, main_window=None):
        super().__init__(parent)
        self.record = record or {}
        self.result_data = None
        self.main_window = main_window
        self.password_visible = False
        self.init_ui(title)

    def init_ui(self, title):
        self.setWindowTitle(title)
        self.setFixedSize(500, 450)
        self.setModal(True)

        layout = QVBoxLayout(self)
        layout.setSpacing(15)

        title_label = QLabel(title)
        title_label.setAlignment(Qt.AlignCenter)
        title_label.setStyleSheet("""
            QLabel {
                font-size: 20px;
                font-weight: bold;
                color: #654321;
                font-family: "Microsoft YaHei";
                padding: 10px;
            }
        """)
        layout.addWidget(title_label)

        form_layout = QVBoxLayout()

        site_name_layout = QHBoxLayout()
        site_name_label = QLabel("网站名称:")
        site_name_label.setFixedWidth(80)
        site_name_label.setStyleSheet("font-family: 'Microsoft YaHei'; color: #654321;")
        self.site_name_input = QLineEdit()
        self.site_name_input.setText(self.record.get("网站名称", ""))
        self.site_name_input.setStyleSheet("""
            QLineEdit {
                border: 1px solid #D9C7A7;
                border-radius: 5px;
                padding: 5px;
                font-family: "Microsoft YaHei";
            }
        """)
        site_name_layout.addWidget(site_name_label)
        site_name_layout.addWidget(self.site_name_input)
        form_layout.addLayout(site_name_layout)

        url_layout = QHBoxLayout()
        url_label = QLabel("网址:")
        url_label.setFixedWidth(80)
        url_label.setStyleSheet("font-family: 'Microsoft YaHei'; color: #654321;")
        self.url_input = QLineEdit()
        self.url_input.setText(self.record.get("网址", ""))
        self.url_input.setStyleSheet(self.site_name_input.styleSheet())
        url_layout.addWidget(url_label)
        url_layout.addWidget(self.url_input)
        form_layout.addLayout(url_layout)

        username_layout = QHBoxLayout()
        username_label = QLabel("账号:")
        username_label.setFixedWidth(80)
        username_label.setStyleSheet("font-family: 'Microsoft YaHei'; color: #654321;")
        self.username_input = QLineEdit()
        self.username_input.setText(self.record.get("账号", ""))
        self.username_input.setStyleSheet(self.site_name_input.styleSheet())
        username_layout.addWidget(username_label)
        username_layout.addWidget(self.username_input)
        form_layout.addLayout(username_layout)

        password_layout = QHBoxLayout()
        password_label = QLabel("密码:")
        password_label.setFixedWidth(80)
        password_label.setStyleSheet("font-family: 'Microsoft YaHei'; color: #654321;")

        self.password_input = QLineEdit()
        self.password_input.setText(self.record.get("密码", ""))
        self.password_input.setEchoMode(QLineEdit.Password)
        self.password_input.setStyleSheet(self.site_name_input.styleSheet())

        self.toggle_password_btn = QPushButton("显示")
        self.toggle_password_btn.setFixedWidth(60)
        self.toggle_password_btn.setStyleSheet("""
            QPushButton {
                background: #8C7853;
                color: white;
                border-radius: 5px;
                font-family: "Microsoft YaHei";
                font-size: 12px;
            }
            QPushButton:hover {
                background: #A8967A;
            }
        """)
        self.toggle_password_btn.clicked.connect(self.toggle_password_visibility)

        self.gen_password_btn = QPushButton("生成")
        self.gen_password_btn.setFixedWidth(60)
        self.gen_password_btn.setStyleSheet(self.toggle_password_btn.styleSheet())
        self.gen_password_btn.clicked.connect(self.open_generator)

        password_layout.addWidget(password_label)
        password_layout.addWidget(self.password_input)
        password_layout.addWidget(self.toggle_password_btn)
        password_layout.addWidget(self.gen_password_btn)
        form_layout.addLayout(password_layout)

        notes_layout = QVBoxLayout()
        notes_label = QLabel("备注:")
        notes_label.setStyleSheet("font-family: 'Microsoft YaHei'; color: #654321;")
        self.notes_input = QTextEdit()
        self.notes_input.setMaximumHeight(100)
        self.notes_input.setStyleSheet("""
            QTextEdit {
                border: 1px solid #D9C7A7;
                border-radius: 5px;
                padding: 5px;
                font-family: "Microsoft YaHei";
            }
        """)
        self.notes_input.setText(self.record.get("备注", ""))
        notes_layout.addWidget(notes_label)
        notes_layout.addWidget(self.notes_input)
        form_layout.addLayout(notes_layout)

        layout.addLayout(form_layout)

        button_layout = QHBoxLayout()
        button_layout.addStretch()

        ok_btn = ChineseStyle.create_gradient_button("确定", 100, 35)
        ok_btn.clicked.connect(self.accept)

        cancel_btn = ChineseStyle.create_gradient_button("取消", 100, 35, "#8C7853", "#A8967A")
        cancel_btn.clicked.connect(self.reject)

        button_layout.addWidget(ok_btn)
        button_layout.addWidget(cancel_btn)
        layout.addLayout(button_layout)

    def toggle_password_visibility(self):
        """切换密码显示/隐藏状态"""
        if not self.password_visible:
            if self.main_window and self.record and "id" in self.record:
                dialog = SecurityDialog(self, "安全验证", "查看密码需要验证您的主密码")
                if dialog.exec_() == QDialog.Accepted:
                    if self.main_window.pm.verify_master_password(dialog.get_password()):
                        self.password_input.setEchoMode(QLineEdit.Normal)
                        self.toggle_password_btn.setText("隐藏")
                        self.password_visible = True
                    else:
                        QMessageBox.warning(self, "验证失败", "主密码错误,无法查看密码")
                else:
                    return
            else:
                self.password_input.setEchoMode(QLineEdit.Normal)
                self.toggle_password_btn.setText("隐藏")
                self.password_visible = True
        else:
            self.password_input.setEchoMode(QLineEdit.Password)
            self.toggle_password_btn.setText("显示")
            self.password_visible = False

    def open_generator(self):
        """打开密码生成器并把结果填入密码框"""
        dialog = PasswordGeneratorDialog(self)
        if dialog.exec_() == QDialog.Accepted:
            pwd = dialog.get_password()
            if pwd:
                self.password_input.setText(pwd)
                self.password_input.setEchoMode(QLineEdit.Normal)
                self.toggle_password_btn.setText("隐藏")
                self.password_visible = True

    def accept(self):
        site_name = self.site_name_input.text().strip()
        if not site_name:
            QMessageBox.warning(self, "输入错误", "请输入网站名称")
            return

        self.result_data = {
            "网站名称": site_name,
            "网址": self.url_input.text().strip(),
            "账号": self.username_input.text().strip(),
            "密码": self.password_input.text(),
            "备注": self.notes_input.toPlainText().strip()
        }
        super().accept()


class ExcelImportDialog(QDialog):
    """Excel导入对话框"""

    import_completed = pyqtSignal(bool, str)

    def __init__(self, parent=None, password_manager=None):
        super().__init__(parent)
        self.pm = password_manager
        self.init_ui()

    def init_ui(self):
        self.setWindowTitle("Excel数据导入")
        self.setFixedSize(500, 600)
        self.setModal(True)

        layout = QVBoxLayout(self)
        layout.setSpacing(15)

        title_label = QLabel("Excel数据导入")
        title_label.setAlignment(Qt.AlignCenter)
        title_label.setStyleSheet("""
            QLabel {
                font-size: 20px;
                font-weight: bold;
                color: #654321;
                font-family: "Microsoft YaHei";
                padding: 10px;
            }
        """)
        layout.addWidget(title_label)

        info_label = QLabel("请选择Excel文件,并指定要导入的目标数据表(单位表或个人表)")
        info_label.setStyleSheet("font-family: 'Microsoft YaHei'; color: #8C7853; font-size: 12px;")
        layout.addWidget(info_label)

        file_group = QGroupBox("选择Excel文件")
        file_group.setStyleSheet("""
            QGroupBox {
                border: 1px solid #D9C7A7;
                border-radius: 8px;
                padding: 10px;
                background: rgba(255, 255, 255, 0.7);
            }
            QGroupBox::title {
                subcontrol-origin: margin;
                left: 10px;
                padding: 0 5px 0 5px;
                color: #8C7853;
            }
        """)

        file_layout = QVBoxLayout(file_group)

        file_path_layout = QHBoxLayout()
        self.file_path_input = QLineEdit()
        self.file_path_input.setPlaceholderText("请选择Excel或CSV文件...")
        self.file_path_input.setStyleSheet("""
            QLineEdit {
                border: 1px solid #D9C7A7;
                border-radius: 5px;
                padding: 5px;
                font-family: "Microsoft YaHei";
            }
        """)
        browse_btn = ChineseStyle.create_gradient_button("浏览", 80, 30)
        browse_btn.clicked.connect(self.browse_file)
        file_path_layout.addWidget(self.file_path_input)
        file_path_layout.addWidget(browse_btn)
        file_layout.addLayout(file_path_layout)

        sheet_layout = QHBoxLayout()
        sheet_label = QLabel("工作表:")
        sheet_label.setFixedWidth(80)
        sheet_label.setStyleSheet("font-family: 'Microsoft YaHei'; color: #654321;")
        self.sheet_combo = QComboBox()
        self.sheet_combo.setStyleSheet("""
            QComboBox {
                border: 1px solid #D9C7A7;
                border-radius: 5px;
                padding: 5px;
                font-family: "Microsoft YaHei";
                min-width: 120px;
            }
        """)
        self.sheet_combo.addItem("自动选择(第一个工作表)")
        sheet_layout.addWidget(sheet_label)
        sheet_layout.addWidget(self.sheet_combo)
        sheet_layout.addStretch()
        file_layout.addLayout(sheet_layout)

        file_layout.addStretch()
        layout.addWidget(file_group)

        target_group = QGroupBox("选择目标数据表")
        target_group.setStyleSheet(file_group.styleSheet())

        target_layout = QVBoxLayout(target_group)

        self.target_radio_group = QButtonGroup()

        self.unit_radio = QRadioButton("单位表")
        self.unit_radio.setChecked(True)
        self.personal_radio = QRadioButton("个人表")

        self.target_radio_group.addButton(self.unit_radio)
        self.target_radio_group.addButton(self.personal_radio)

        target_layout.addWidget(self.unit_radio)
        target_layout.addWidget(self.personal_radio)
        target_layout.addStretch()

        layout.addWidget(target_group)

        options_group = QGroupBox("导入选项")
        options_group.setStyleSheet(file_group.styleSheet())

        options_layout = QVBoxLayout(options_group)

        mapping_label = QLabel("字段映射说明:")
        mapping_label.setStyleSheet(
            "font-family: 'Microsoft YaHei'; color: #654321; font-weight: bold; font-size: 12px;")
        options_layout.addWidget(mapping_label)

        mapping_text = QLabel("""
        &#8226; 系统会自动匹配常见的Excel/CSV列名,如:网站名称、网站名、网址、URL、账号、账户、密码等
        &#8226; 如果列名不匹配,系统会按照顺序尝试匹配
        &#8226; 必填字段:网站名称或账号至少填写一项
        &#8226; 支持.xlsx、.xls 格式的Excel文件,以及 .csv 文件(UTF-8编码)
        """)
        mapping_text.setStyleSheet("font-family: 'Microsoft YaHei'; color: #8C7853; font-size: 11px;")
        mapping_text.setWordWrap(True)
        options_layout.addWidget(mapping_text)

        options_layout.addStretch()
        layout.addWidget(options_group)

        self.progress_bar = QProgressBar()
        self.progress_bar.setRange(0, 100)
        self.progress_bar.setValue(0)
        self.progress_bar.setTextVisible(True)
        self.progress_bar.setStyleSheet("""
            QProgressBar {
                border: 1px solid #D9C7A7;
                border-radius: 5px;
                text-align: center;
                font-family: "Microsoft YaHei";
                color: #654321;
            }
            QProgressBar::chunk {
                background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
                                            stop:0 #A64D37, stop:1 #C46C4E);
                border-radius: 3px;
            }
        """)
        self.progress_bar.setVisible(False)
        layout.addWidget(self.progress_bar)

        button_layout = QHBoxLayout()
        button_layout.addStretch()

        self.import_btn = ChineseStyle.create_gradient_button("开始导入", 100, 35)
        self.import_btn.clicked.connect(self.start_import)
        button_layout.addWidget(self.import_btn)

        self.cancel_btn = ChineseStyle.create_gradient_button("取消", 100, 35, "#8C7853", "#A8967A")
        self.cancel_btn.clicked.connect(self.reject)
        button_layout.addWidget(self.cancel_btn)

        layout.addLayout(button_layout)

    def browse_file(self):
        """浏览选择Excel或CSV文件"""
        file_path, _ = QFileDialog.getOpenFileName(
            self,
            "选择Excel/CSV文件",
            "",
            "Excel/CSV文件 (*.xlsx *.xls *.csv);;所有文件 (*)"
        )
        if file_path:
            self.file_path_input.setText(file_path)
            self.load_sheet_info(file_path)

    def load_sheet_info(self, file_path):
        """加载Excel文件的工作表信息;CSV文件无工作表"""
        self.sheet_combo.clear()
        if file_path.lower().endswith('.csv'):
            self.sheet_combo.addItem("CSV文件(无工作表)")
            self.sheet_combo.setEnabled(False)
            return

        self.sheet_combo.setEnabled(True)
        self.sheet_combo.addItem("自动选择(第一个工作表)")
        try:
            xls = pd.ExcelFile(file_path)
            sheets = xls.sheet_names
            for sheet in sheets:
                self.sheet_combo.addItem(sheet)
        except Exception as e:
            QMessageBox.warning(self, "文件错误", f"无法读取Excel文件: {str(e)}")

    def start_import(self):
        """开始导入过程"""
        file_path = self.file_path_input.text().strip()
        if not file_path:
            QMessageBox.warning(self, "选择文件", "请先选择Excel文件")
            return

        if not os.path.exists(file_path):
            QMessageBox.warning(self, "文件不存在", "所选的Excel文件不存在")
            return

        if not (file_path.lower().endswith('.xlsx') or file_path.lower().endswith('.xls') or file_path.lower().endswith('.csv')):
            QMessageBox.warning(self, "文件格式", "请选择.xlsx、.xls或.csv格式的文件")
            return

        if self.unit_radio.isChecked():
            target_table = "单位表"
        else:
            target_table = "个人表"

        sheet_selection = self.sheet_combo.currentText()
        sheet_name = None
        if sheet_selection not in ("自动选择(第一个工作表)", "CSV文件(无工作表)"):
            sheet_name = sheet_selection

        # 验证主密码
        dialog = SecurityDialog(self, "安全验证", "导入数据需要验证您的主密码")
        if dialog.exec_() == QDialog.Accepted:
            if self.pm.verify_master_password(dialog.get_password()):
                self.set_interaction_enabled(False)
                self.progress_bar.setVisible(True)
                self.progress_bar.setValue(0)

                # 在线程中执行导入
                self.import_thread = ImportThread(file_path, target_table, sheet_name, self.pm)
                self.import_thread.progress_updated.connect(self.update_progress)
                self.import_thread.import_completed.connect(self.on_import_completed)
                self.import_thread.start()
            else:
                QMessageBox.warning(self, "验证失败", "主密码错误,无法导入数据")

    def set_interaction_enabled(self, enabled):
        """设置界面元素交互状态"""
        self.file_path_input.setEnabled(enabled)
        self.sheet_combo.setEnabled(enabled)
        self.unit_radio.setEnabled(enabled)
        self.personal_radio.setEnabled(enabled)
        self.import_btn.setEnabled(enabled)
        self.cancel_btn.setEnabled(enabled)

    def update_progress(self, value):
        """更新进度条"""
        self.progress_bar.setValue(value)

    def on_import_completed(self, success, message):
        """导入完成回调"""
        self.set_interaction_enabled(True)
        self.progress_bar.setVisible(False)

        if success:
            QMessageBox.information(self, "导入成功", message)
            self.import_completed.emit(True, message)
            self.accept()
        else:
            QMessageBox.critical(self, "导入失败", message)
            self.import_completed.emit(False, message)


class ExportDialog(QDialog):
    """数据导出选项对话框(功能6,支持勾选记录)"""

    def __init__(self, parent=None, current_table="单位表", data=None):
        super().__init__(parent)
        self.current_table = current_table
        self.data = data or {}  # {表名: [记录dict, ...]}
        self.setWindowTitle("导出数据")
        self.setFixedSize(460, 520)
        self.setWindowIcon(get_app_icon())
        layout = QVBoxLayout(self)
        layout.setSpacing(10)

        fmt_label = QLabel("导出格式:")
        fmt_label.setStyleSheet("font-family: 'Microsoft YaHei'; color: #654321; font-weight: bold;")
        layout.addWidget(fmt_label)
        self.excel_rb = QRadioButton("Excel 文件 (.xlsx)")
        self.csv_rb = QRadioButton("CSV 文件 (.csv)")
        self.excel_rb.setChecked(True)
        for rb in (self.excel_rb, self.csv_rb):
            rb.setStyleSheet("font-family: 'Microsoft YaHei';")
            layout.addWidget(rb)

        scope_label = QLabel("导出范围:")
        scope_label.setStyleSheet("font-family: 'Microsoft YaHei'; color: #654321; font-weight: bold;")
        layout.addWidget(scope_label)
        self.current_rb = QRadioButton(f"仅当前表({current_table})")
        self.all_rb = QRadioButton("全部(单位表 + 个人表)")
        self.current_rb.setChecked(True)
        for rb in (self.current_rb, self.all_rb):
            rb.setStyleSheet("font-family: 'Microsoft YaHei';")
            layout.addWidget(rb)
            rb.toggled.connect(self._load_records)
        # 显式按钮组,保证格式/范围各自互斥
        self.fmt_group = QButtonGroup(self)
        self.fmt_group.addButton(self.excel_rb)
        self.fmt_group.addButton(self.csv_rb)
        self.scope_group = QButtonGroup(self)
        self.scope_group.addButton(self.current_rb)
        self.scope_group.addButton(self.all_rb)

        # 记录勾选列表
        list_tip = QLabel("选择要导出的记录(可勾选 / 全选):")
        list_tip.setStyleSheet("font-family: 'Microsoft YaHei'; color: #654321; font-weight: bold;")
        layout.addWidget(list_tip)
        self.record_list = QListWidget()
        self.record_list.setStyleSheet(
            "font-family: 'Microsoft YaHei'; border: 1px solid #D9C7A7; "
            "border-radius: 6px; background: #FFFEF9;")
        layout.addWidget(self.record_list, 1)

        sel_layout = QHBoxLayout()
        self.select_all_btn = ChineseStyle.create_gradient_button("全选", 80, 30)
        self.select_all_btn.clicked.connect(self._select_all)
        self.select_none_btn = ChineseStyle.create_gradient_button("全不选", 80, 30, "#8C7853", "#A8967A")
        self.select_none_btn.clicked.connect(self._select_none)
        sel_layout.addStretch()
        sel_layout.addWidget(self.select_all_btn)
        sel_layout.addWidget(self.select_none_btn)
        layout.addLayout(sel_layout)

        self._load_records()

        mp_label = QLabel("主密码(导出须验证,导出内容为明文):")
        mp_label.setStyleSheet("font-family: 'Microsoft YaHei'; color: #654321; font-weight: bold;")
        layout.addWidget(mp_label)
        self.mp_input = QLineEdit()
        self.mp_input.setEchoMode(QLineEdit.Password)
        self.mp_input.setPlaceholderText("请输入主密码以授权导出")
        self.mp_input.setStyleSheet(
            "font-family: 'Microsoft YaHei'; padding: 6px; "
            "border: 1px solid #D9C7A7; border-radius: 6px;")
        layout.addWidget(self.mp_input)

        btn_layout = QHBoxLayout()
        btn_layout.addStretch()
        ok_btn = ChineseStyle.create_gradient_button("确定", 100, 35)
        ok_btn.clicked.connect(self.accept)
        cancel_btn = ChineseStyle.create_gradient_button("取消", 100, 35, "#8C7853", "#A8967A")
        cancel_btn.clicked.connect(self.reject)
        btn_layout.addWidget(ok_btn)
        btn_layout.addWidget(cancel_btn)
        layout.addLayout(btn_layout)

    def _load_records(self):
        """根据范围把记录加载到列表(每项带勾选框,默认全选)"""
        self.record_list.clear()
        tables = [self.current_table] if self.scope_group.checkedButton() is self.current_rb else ["单位表", "个人表"]
        multi = len(tables) > 1
        for t in tables:
            for rec in self.data.get(t, []):
                name = rec.get("网站名称", "")
                account = rec.get("账号", "")
                tag = f"[{t}] " if multi else ""
                item = QListWidgetItem(f"{tag}{name}  |  {account}")
                item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
                item.setCheckState(Qt.Checked)
                item.setData(Qt.UserRole, {"table": t, "record": rec})
                self.record_list.addItem(item)

    def _select_all(self):
        for i in range(self.record_list.count()):
            self.record_list.item(i).setCheckState(Qt.Checked)

    def _select_none(self):
        for i in range(self.record_list.count()):
            self.record_list.item(i).setCheckState(Qt.Unchecked)

    def get_selected(self):
        """返回勾选记录列表:[{'table':.., 'record':..}, ...]"""
        result = []
        for i in range(self.record_list.count()):
            item = self.record_list.item(i)
            if item.checkState() == Qt.Checked:
                result.append(item.data(Qt.UserRole))
        return result

    def get_choice(self):
        return "excel" if self.excel_rb.isChecked() else "csv"

    def get_scope(self):
        return "current" if self.current_rb.isChecked() else "all"

    def get_master_password(self):
        return self.mp_input.text().strip()


class IdleSettingsDialog(QDialog):
    """空闲自动锁定时间设置(功能3 可配置,从欢迎菜单进入)"""

    def __init__(self, parent=None, current_minutes=DEFAULT_IDLE_MINUTES):
        super().__init__(parent)
        self.setWindowTitle("空闲自动锁定设置")
        self.setFixedSize(360, 180)
        self.setWindowIcon(get_app_icon())
        layout = QVBoxLayout(self)
        layout.setSpacing(14)

        tip = QLabel("设置无操作后自动锁定的时间(锁定时需重新输入主密码进入):")
        tip.setStyleSheet("font-family: 'Microsoft YaHei'; color: #654321;")
        tip.setWordWrap(True)
        layout.addWidget(tip)

        row = QHBoxLayout()
        row.addStretch()
        self.spin = QSpinBox()
        self.spin.setRange(1, 120)
        self.spin.setValue(current_minutes)
        self.spin.setSuffix(" 分钟")
        self.spin.setStyleSheet("font-family: 'Microsoft YaHei'; font-size: 16px;")
        row.addWidget(self.spin)
        row.addStretch()
        layout.addLayout(row)

        layout.addStretch()
        btn = QHBoxLayout()
        btn.addStretch()
        ok = ChineseStyle.create_gradient_button("确定", 100, 35)
        ok.clicked.connect(self.accept)
        cancel = ChineseStyle.create_gradient_button("取消", 100, 35, "#8C7853", "#A8967A")
        cancel.clicked.connect(self.reject)
        btn.addWidget(ok)
        btn.addWidget(cancel)
        layout.addLayout(btn)

    def get_minutes(self):
        return self.spin.value()


class ShareDialog(QDialog):
    """分享记录对话框"""

    def __init__(self, parent=None, record=None):
        super().__init__(parent)
        self.record = record or {}
        self.init_ui()

    def init_ui(self):
        self.setWindowTitle("分享账号信息")
        self.setFixedSize(500, 400)
        self.setModal(True)

        layout = QVBoxLayout(self)
        layout.setSpacing(15)

        # 标题
        title_label = QLabel("分享账号信息")
        title_label.setAlignment(Qt.AlignCenter)
        title_label.setStyleSheet("""
            QLabel {
                font-size: 20px;
                font-weight: bold;
                color: #654321;
                font-family: "Microsoft YaHei";
                padding: 10px;
            }
        """)
        layout.addWidget(title_label)

        # 分享格式说明
        info_label = QLabel("以下信息已按标准格式生成,可直接复制使用:")
        info_label.setStyleSheet("font-family: 'Microsoft YaHei'; color: #8C7853;")
        layout.addWidget(info_label)

        # 分享内容文本框
        self.share_text = QTextEdit()
        self.share_text.setStyleSheet("""
            QTextEdit {
                border: 1px solid #D9C7A7;
                border-radius: 8px;
                padding: 10px;
                font-family: "Microsoft YaHei";
                font-size: 14px;
                background-color: #FFFEF9;
                selection-background-color: #E8D0B0;
            }
        """)
        self.share_text.setReadOnly(True)
        layout.addWidget(self.share_text)

        # 生成分享内容
        self.generate_share_content()

        # 按钮布局
        button_layout = QHBoxLayout()
        button_layout.addStretch()

        copy_btn = ChineseStyle.create_gradient_button("复制内容", 100, 35)
        copy_btn.clicked.connect(self.copy_to_clipboard)

        close_btn = ChineseStyle.create_gradient_button("关闭", 100, 35, "#8C7853", "#A8967A")
        close_btn.clicked.connect(self.accept)

        button_layout.addWidget(copy_btn)
        button_layout.addWidget(close_btn)
        layout.addLayout(button_layout)

    def generate_share_content(self):
        """生成分享内容"""
        website_name = self.record.get("网站名称", "未知网站")
        website_url = self.record.get("网址", "")
        account = self.record.get("账号", "")
        password = self.record.get("密码", "")
        description = self.record.get("备注", "")

        # 创建一个列表,用于存储非空的字段信息
        fields = []

        # 检查每个字段是否为空,非空则添加到列表中
        if website_name and website_name != "未知网站":
            fields.append(f"网站名称:{website_name}")

        if website_url:
            fields.append(f"网址:{website_url}")

        if account:
            fields.append(f"账号:{account}")

        if password:
            fields.append(f"密码:{password}")

        if description:
            fields.append(f"备注:{description}")

        # 将列表中的字段用换行符连接起来
        share_content = "\n".join(fields)

        self.share_text.setPlainText(share_content)

    def copy_to_clipboard(self):
        """复制内容到剪贴板"""
        clipboard = QApplication.clipboard()
        clipboard.setText(self.share_text.toPlainText())

        # 显示复制成功提示
        QMessageBox.information(self, "复制成功", "分享内容已复制到剪贴板!")


class ImportThread(QThread):
    """导入数据的后台线程"""

    progress_updated = pyqtSignal(int)
    import_completed = pyqtSignal(bool, str)

    def __init__(self, file_path, target_table, sheet_name, password_manager):
        super().__init__()
        self.file_path = file_path
        self.target_table = target_table
        self.sheet_name = sheet_name
        self.pm = password_manager

    def run(self):
        """线程运行函数"""
        try:
            df = _read_import_dataframe(self.file_path, self.sheet_name)

            if df.empty:
                self.import_completed.emit(False, "文件为空或没有数据")
                return

            field_mapping = {
                '网站名称': ['网站名称', '网站名', '网站', '名称', '站点名称', '平台名称'],
                '网址': ['网址', '网站地址', 'URL', '链接', '地址', '网站链接'],
                '账号': ['账号', '账户', '用户名', '用户', '登录名', '用户账号'],
                '密码': ['密码', '登陆密码', '登录密码', 'pass', 'passwd'],
                '备注': ['备注', '说明', '注释', '描述', 'note', 'description']
            }

            actual_mapping = {}
            for target_field, possible_names in field_mapping.items():
                for col in df.columns:
                    if any(name in str(col) for name in possible_names):
                        actual_mapping[target_field] = col
                        break
                else:
                    actual_mapping[target_field] = None

            total_rows = len(df)
            imported_count = 0
            skipped_count = 0
            error_count = 0

            for index, row in df.iterrows():
                try:
                    # 更新进度
                    progress = int((index + 1) / total_rows * 100)
                    self.progress_updated.emit(progress)

                    website_name = row[actual_mapping['网站名称']] if actual_mapping['网站名称'] is not None else ""
                    website_url = row[actual_mapping['网址']] if actual_mapping['网址'] is not None else ""
                    account = row[actual_mapping['账号']] if actual_mapping['账号'] is not None else ""
                    password = row[actual_mapping['密码']] if actual_mapping['密码'] is not None else ""
                    notes = row[actual_mapping['备注']] if actual_mapping['备注'] is not None else ""

                    website_name = str(website_name).strip() if pd.notna(website_name) else ""
                    website_url = str(website_url).strip() if pd.notna(website_url) else ""
                    account = str(account).strip() if pd.notna(account) else ""
                    password = str(password).strip() if pd.notna(password) else ""
                    notes = str(notes).strip() if pd.notna(notes) else ""

                    if not website_name and not account:
                        skipped_count += 1
                        continue

                    record_data = {
                        "网站名称": website_name,
                        "网址": website_url,
                        "账号": account,
                        "密码": password,
                        "备注": notes
                    }

                    if self.pm.add_record(self.target_table, record_data):
                        imported_count += 1
                    else:
                        error_count += 1
                        skipped_count += 1

                except Exception as e:
                    error_count += 1
                    skipped_count += 1
                    continue

            self.progress_updated.emit(100)
            self.import_completed.emit(True,
                                       f"导入完成:成功导入 {imported_count} 条记录,跳过 {skipped_count} 条记录,错误 {error_count} 条记录")

        except Exception as e:
            self.import_completed.emit(False, f"导入过程中发生错误: {str(e)}")


class MainWindow(QMainWindow):
    """主窗口 - 添加右键菜单功能"""

    def __init__(self, password_manager, login_window, app_controller):  # 修改:添加 app_controller 参数
        super().__init__()
        self.pm = password_manager
        self.login_window = login_window
        self.app_controller = app_controller  # 新增:保存应用程序控制器引用
        self.current_table = "单位表"
        self.current_records = []
        self.init_ui()

    def init_ui(self):
        self.setWindowTitle(f"墨香 - {self.pm.current_user}")
        self.setGeometry(100, 100, 1200, 800)
        self.setWindowIcon(get_app_icon())

        central_widget = QWidget()
        self.setCentralWidget(central_widget)

        main_layout = QVBoxLayout(central_widget)
        main_layout.setSpacing(10)
        main_layout.setContentsMargins(15, 15, 15, 15)

        # 标题栏
        title_layout = QHBoxLayout()

        title_label = QLabel("墨香")
        title_label.setStyleSheet("""
            QLabel {
                font-size: 24px;
                font-weight: bold;
                color: #654321;
                font-family: "Microsoft YaHei";
                padding: 5px;
            }
        """)
        title_layout.addWidget(title_label)

        title_layout.addStretch()

        # 用户信息标签 - 设置为支持右键菜单
        self.user_info_label = QLabel(f"欢迎,{self.pm.current_user}")
        self.user_info_label.setStyleSheet("""
            QLabel {
                font-family: 'Microsoft YaHei'; 
                color: #8C7853;
                padding: 5px;
                border: 1px solid transparent;
                border-radius: 5px;
            }
            QLabel:hover {
                background-color: rgba(217, 199, 167, 0.2);
                border: 1px solid #D9C7A7;
            }
        """)
        self.user_info_label.setContextMenuPolicy(Qt.CustomContextMenu)
        self.user_info_label.customContextMenuRequested.connect(self.show_account_menu)
        title_layout.addWidget(self.user_info_label)

        main_layout.addLayout(title_layout)

        # 控制面板
        control_frame = QGroupBox("控制面板")
        control_frame.setStyleSheet("""
            QGroupBox {
                border: 1px solid #D9C7A7;
                border-radius: 8px;
                margin-top: 10px;
                padding-top: 10px;
                font-family: "Microsoft YaHei";
                color: #654321;
                font-weight: bold;
            }
            QGroupBox::title {
                subcontrol-origin: margin;
                left: 10px;
                padding: 0 5px 0 5px;
            }
        """)

        control_layout = QVBoxLayout(control_frame)

        # 第一行控制按钮
        control_row1 = QHBoxLayout()

        table_label = QLabel("数据表:")
        table_label.setStyleSheet("font-family: 'Microsoft YaHei';")
        control_row1.addWidget(table_label)

        self.table_combo = QComboBox()
        self.table_combo.addItems(["单位表", "个人表"])
        self.table_combo.setCurrentText(self.current_table)
        self.table_combo.currentTextChanged.connect(self.on_table_changed)
        self.table_combo.setStyleSheet("""
            QComboBox {
                border: 1px solid #D9C7A7;
                border-radius: 5px;
                padding: 5px;
                font-family: "Microsoft YaHei";
                min-width: 100px;
            }
        """)
        control_row1.addWidget(self.table_combo)

        control_row1.addSpacing(20)

        self.add_btn = ChineseStyle.create_gradient_button("新增记录", 100, 30)
        self.add_btn.clicked.connect(self.add_record)
        control_row1.addWidget(self.add_btn)

        self.edit_btn = ChineseStyle.create_gradient_button("编辑记录", 100, 30)
        self.edit_btn.clicked.connect(self.edit_record)
        control_row1.addWidget(self.edit_btn)

        self.delete_btn = ChineseStyle.create_gradient_button("删除记录", 100, 30)
        self.delete_btn.clicked.connect(self.delete_record)
        control_row1.addWidget(self.delete_btn)

        self.view_password_btn = ChineseStyle.create_gradient_button("查看密码", 100, 30)
        self.view_password_btn.clicked.connect(self.view_password)
        control_row1.addWidget(self.view_password_btn)

        self.refresh_btn = ChineseStyle.create_gradient_button("刷新数据", 100, 30)
        self.refresh_btn.clicked.connect(self.refresh_table)
        control_row1.addWidget(self.refresh_btn)

        self.import_btn = ChineseStyle.create_gradient_button("Excel导入", 100, 30)
        self.import_btn.clicked.connect(self.excel_import)
        control_row1.addWidget(self.import_btn)

        self.share_btn = ChineseStyle.create_gradient_button("分享记录", 100, 30)
        self.share_btn.clicked.connect(self.share_record)
        control_row1.addWidget(self.share_btn)

        self.export_btn = ChineseStyle.create_gradient_button("导出数据", 100, 30, "#8C7853", "#A8967A")
        self.export_btn.clicked.connect(self.export_data)
        control_row1.addWidget(self.export_btn)

        control_row1.addStretch()
        control_layout.addLayout(control_row1)

        # 第二行搜索功能
        control_row2 = QHBoxLayout()

        search_label = QLabel("搜   索:")
        search_label.setStyleSheet("font-family: 'Microsoft YaHei';")
        control_row2.addWidget(search_label)

        self.search_field_combo = QComboBox()
        self.search_field_combo.addItems(["所有字段", "网站名称", "网址", "账号", "备注"])
        self.search_field_combo.setStyleSheet(self.table_combo.styleSheet())
        control_row2.addWidget(self.search_field_combo)

        self.search_input = QLineEdit()
        self.search_input.setPlaceholderText("输入搜索关键词...")
        self.search_input.textChanged.connect(self.on_search)
        self.search_input.setStyleSheet("""
            QLineEdit {
                border: 1px solid #D9C7A7;
                border-radius: 5px;
                padding: 5px;
                font-family: "Microsoft YaHei";
            }
        """)
        control_row2.addWidget(self.search_input)

        self.clear_search_btn = ChineseStyle.create_gradient_button("清空搜索", 80, 30, "#8C7853", "#A8967A")
        self.clear_search_btn.clicked.connect(self.clear_search)
        control_row2.addWidget(self.clear_search_btn)

        control_row2.addStretch()
        control_layout.addLayout(control_row2)

        main_layout.addWidget(control_frame)

        # 数据表格
        table_frame = QGroupBox("数据记录")
        table_frame.setStyleSheet(control_frame.styleSheet())

        table_layout = QVBoxLayout(table_frame)

        self.table_widget = QTableWidget()
        self.table_widget.setColumnCount(6)
        self.table_widget.setHorizontalHeaderLabels(["ID", "网站名称", "网址", "账号", "密码", "备注"])

        self.table_widget.setStyleSheet("""
            QTableWidget {
                border: 1px solid #D9C7A7;
                border-radius: 5px;
                background-color: #FFFEF9;
                alternate-background-color: #F8F4E9;
                font-family: "Microsoft YaHei";
                gridline-color: #D9C7A7;
            }
            QTableWidget::item {
                padding: 5px;
                border-bottom: 1px solid #E8E0D0;
            }
            QTableWidget::item:selected {
                background-color: #E8D0B0;
                color: #654321;
            }
            QHeaderView::section {
                background-color: #D9C7A7;
                color: #654321;
                font-weight: bold;
                padding: 5px;
                border: none;
                font-family: "Microsoft YaHei";
            }
        """)

        header = self.table_widget.horizontalHeader()
        header.setSectionResizeMode(0, QHeaderView.ResizeToContents)
        header.setSectionResizeMode(1, QHeaderView.Stretch)
        header.setSectionResizeMode(2, QHeaderView.Stretch)
        header.setSectionResizeMode(3, QHeaderView.Stretch)
        header.setSectionResizeMode(4, QHeaderView.ResizeToContents)
        header.setSectionResizeMode(5, QHeaderView.Stretch)

        self.table_widget.setAlternatingRowColors(True)
        self.table_widget.setSelectionBehavior(QTableWidget.SelectRows)
        self.table_widget.itemDoubleClicked.connect(self.on_item_double_clicked)

        table_layout.addWidget(self.table_widget)
        main_layout.addWidget(table_frame)

        # 状态栏
        status_bar = QHBoxLayout()

        self.status_label = QLabel("就绪")
        self.status_label.setStyleSheet("font-family: 'Microsoft YaHei'; color: #8C7853;")
        status_bar.addWidget(self.status_label)

        status_bar.addStretch()

        self.record_count_label = QLabel("")
        self.record_count_label.setStyleSheet("font-family: 'Microsoft YaHei'; color: #8C7853;")
        status_bar.addWidget(self.record_count_label)

        main_layout.addLayout(status_bar)

        self.refresh_table()
        self.update_status("系统就绪")

        # ===== 空闲自动锁定(功能3,空闲时长可配置)=====
        self.idle_timeout = load_config().get("idle_minutes", DEFAULT_IDLE_MINUTES) * 60
        self.last_activity = time.time()
        self.is_locked = False
        self._relocking = False
        self.installEventFilter(self)
        self.idle_timer = QTimer(self)
        self.idle_timer.timeout.connect(self.check_idle)
        self.idle_timer.start(1000)      # 每秒检测一次空闲

    def eventFilter(self, obj, event):
        """监听用户鼠标/键盘活动,重置空闲计时"""
        if event.type() in (QEvent.MouseMove, QEvent.MouseButtonPress,
                            QEvent.MouseButtonRelease, QEvent.KeyPress,
                            QEvent.KeyRelease, QEvent.Wheel):
            self.last_activity = time.time()
        return super().eventFilter(obj, event)

    def check_idle(self):
        """空闲超时则自动锁定"""
        if self.is_locked:
            return
        # 有模态对话框打开时视为活动中,避免误锁
        if QApplication.activeModalWidget() is not None:
            self.last_activity = time.time()
            return
        if time.time() - self.last_activity >= self.idle_timeout:
            self.auto_lock()

    def auto_lock(self):
        """空闲自动锁定:隐藏主窗口并回到登录界面(需主密码重新进入)"""
        self.is_locked = True
        self.idle_timer.stop()
        self._relocking = True
        if self.app_controller.tray_icon:
            self.app_controller.tray_icon.showMessage(
                "墨香密码管理器", "已因长时间无操作自动锁定", QSystemTrayIcon.Information, 2000)
        self.hide()
        self.login_window.show_login()

    def show_account_menu(self, pos):
        """显示账号右键菜单(含空闲锁定设置入口)"""
        menu = QMenu(self)

        backup_action = QAction("&#128202; 手动备份", self)
        change_pwd_action = QAction("&#128273; 修改密码", self)
        idle_action = QAction("&#9202; 空闲锁定设置", self)
        logout_action = QAction("&#128682; 注销账号", self)

        menu.addAction(backup_action)
        menu.addAction(change_pwd_action)
        menu.addAction(idle_action)
        menu.addSeparator()
        menu.addAction(logout_action)

        backup_action.triggered.connect(self.manual_backup)
        change_pwd_action.triggered.connect(self.change_password)
        idle_action.triggered.connect(self.open_idle_settings)
        logout_action.triggered.connect(self.logout)

        menu.exec_(self.user_info_label.mapToGlobal(pos))

    def open_idle_settings(self):
        """打开空闲自动锁定时间设置,并持久化到配置"""
        minutes = int(self.idle_timeout // 60)
        dlg = IdleSettingsDialog(self, minutes)
        if dlg.exec_() == QDialog.Accepted:
            new_min = dlg.get_minutes()
            self.idle_timeout = new_min * 60
            cfg = load_config()
            cfg["idle_minutes"] = new_min
            save_config(cfg)
            self.update_status(f"空闲自动锁定已设为 {new_min} 分钟")
            QMessageBox.information(self, "设置已保存",
                                    f"空闲自动锁定时间已设为 {new_min} 分钟。\n下次登录生效(当前会话立即生效)。")

    def on_item_double_clicked(self, item):
        """处理表格项双击事件"""
        row = item.row()
        column = item.column()

        if column == 2:  # 网址列
            url_item = self.table_widget.item(row, 2)
            if url_item and url_item.text():
                self.open_url_in_browser(url_item.text())
        elif column == 4:  # 密码列
            self.view_password()
        else:  # 其他列
            self.edit_record()

    def open_url_in_browser(self, url):
        """在默认浏览器中打开网址"""
        if not url:
            QMessageBox.warning(self, "网址为空", "该记录的网址为空,无法打开。")
            return

        if not url.startswith(('http://', 'https://')):
            url = 'http://' + url

        try:
            webbrowser.open(url)
            self.update_status(f"正在打开网址: {url}")
        except Exception as e:
            QMessageBox.critical(self, "打开失败", f"无法打开网址: {str(e)}")

    def on_table_changed(self, table_name):
        self.current_table = table_name
        self.refresh_table()
        self.update_status(f"已切换到 {self.current_table}")

    def on_search(self):
        search_term = self.search_input.text()
        search_field = self.search_field_combo.currentText()
        self.current_records = self.pm.search_records(self.current_table, search_term, search_field)
        self.update_table()
        self.update_status(f"搜索完成,找到 {len(self.current_records)} 条记录")

    def clear_search(self):
        self.search_input.clear()
        self.refresh_table()

    def refresh_table(self):
        self.current_records = self.pm.current_data["tables"][self.current_table]
        self.update_table()
        self.update_status("数据已刷新")

    def update_table(self):
        """更新表格显示"""
        self.table_widget.setRowCount(0)

        for record in self.current_records:
            row = self.table_widget.rowCount()
            self.table_widget.insertRow(row)

            # 密码显示为星号
            display_pwd = "&#8226;" * 8 if record.get("密码") else ""

            self.table_widget.setItem(row, 0, QTableWidgetItem(str(record["id"])))
            self.table_widget.setItem(row, 1, QTableWidgetItem(record.get("网站名称", "")))

            # 网址列特殊处理:添加超链接样式
            url_item = QTableWidgetItem(record.get("网址", ""))
            url_item.setForeground(QColor(166, 77, 55))  # 朱红色
            url_item.setToolTip("双击打开此网址")
            self.table_widget.setItem(row, 2, url_item)

            self.table_widget.setItem(row, 3, QTableWidgetItem(record.get("账号", "")))
            self.table_widget.setItem(row, 4, QTableWidgetItem(display_pwd))
            self.table_widget.setItem(row, 5, QTableWidgetItem(record.get("备注", "")))

        # 更新记录计数
        total_records = len(self.pm.current_data["tables"]["单位表"]) + len(self.pm.current_data["tables"]["个人表"])
        self.record_count_label.setText(f"总记录数: {total_records}")

    def add_record(self):
        """添加新记录"""
        dialog = RecordDialog(self, "新增记录", main_window=self)
        if dialog.exec_() == QDialog.Accepted and dialog.result_data:
            try:
                if self.pm.add_record(self.current_table, dialog.result_data):
                    self.refresh_table()
                    QMessageBox.information(self, "成功", "记录添加成功")
                    self.update_status("记录添加成功")
                else:
                    QMessageBox.critical(self, "错误", "添加记录失败")
            except Exception as e:
                QMessageBox.critical(self, "错误", f"添加记录时出错: {str(e)}")

    def edit_record(self):
        """编辑记录"""
        selected = self.table_widget.selectedItems()
        if not selected:
            QMessageBox.warning(self, "警告", "请选择要编辑的记录")
            return

        row = self.table_widget.currentRow()
        record_id = int(self.table_widget.item(row, 0).text())

        # 查找原始记录
        original_record = None
        for record in self.pm.current_data["tables"][self.current_table]:
            if record["id"] == record_id:
                original_record = record
                break

        if not original_record:
            QMessageBox.critical(self, "错误", "未找到记录")
            return

        # 验证主密码
        dialog = SecurityDialog(self, "安全验证", "编辑记录需要验证您的主密码")
        if dialog.exec_() == QDialog.Accepted:
            if self.pm.verify_master_password(dialog.get_password()):
                dialog = RecordDialog(self, "编辑记录", original_record, main_window=self)
                if dialog.exec_() == QDialog.Accepted and dialog.result_data:
                    if self.pm.update_record(self.current_table, record_id, dialog.result_data):
                        self.refresh_table()
                        QMessageBox.information(self, "成功", "记录更新成功")
                        self.update_status("记录更新成功")
                    else:
                        QMessageBox.critical(self, "错误", "更新记录失败")
            else:
                QMessageBox.warning(self, "验证失败", "主密码错误,无法编辑记录")

    def view_password(self):
        """查看密码功能"""
        selected = self.table_widget.selectedItems()
        if not selected:
            QMessageBox.warning(self, "警告", "请选择要查看密码的记录")
            return

        row = self.table_widget.currentRow()
        record_id = int(self.table_widget.item(row, 0).text())

        # 查找原始记录
        original_record = None
        for record in self.pm.current_data["tables"][self.current_table]:
            if record["id"] == record_id:
                original_record = record
                break

        if not original_record:
            QMessageBox.critical(self, "错误", "未找到记录")
            return

        # 验证主密码
        dialog = SecurityDialog(self, "安全验证", "查看密码需要验证您的主密码")
        if dialog.exec_() == QDialog.Accepted:
            if self.pm.verify_master_password(dialog.get_password()):
                site_name = original_record.get("网站名称", "")
                username = original_record.get("账号", "")
                password = original_record.get("密码", "")

                # 显示密码信息
                msg = QMessageBox(self)
                msg.setWindowTitle("密码详情")
                msg.setText(f"""
                <div style="font-family: 'Microsoft YaHei'; color: #654321;">
                    <h3>密码详情</h3>
                    <p><b>网站名称:</b> {site_name}</p>
                    <p><b>账号:</b> {username}</p>
                    <p><b>密码:</b> <span style="color: #A64D37; font-weight: bold;">{password}</span></p>
                </div>
                """)
                msg.setStandardButtons(QMessageBox.Ok)
                msg.exec_()
            else:
                QMessageBox.warning(self, "验证失败", "主密码错误,无法查看密码")

    def delete_record(self):
        """删除记录"""
        selected = self.table_widget.selectedItems()
        if not selected:
            QMessageBox.warning(self, "警告", "请选择要删除的记录")
            return

        row = self.table_widget.currentRow()
        record_id = int(self.table_widget.item(row, 0).text())

        reply = QMessageBox.question(self, "确认删除",
                                     "确定要删除选中的记录吗?",
                                     QMessageBox.Yes | QMessageBox.No)

        if reply == QMessageBox.Yes:
            if self.pm.delete_record(self.current_table, record_id):
                self.refresh_table()
                QMessageBox.information(self, "成功", "记录删除成功")
                self.update_status("记录删除成功")
            else:
                QMessageBox.critical(self, "错误", "删除记录失败")

    def share_record(self):
        """分享选中的记录"""
        selected = self.table_widget.selectedItems()
        if not selected:
            QMessageBox.warning(self, "警告", "请选择要分享的记录")
            return

        row = self.table_widget.currentRow()
        record_id = int(self.table_widget.item(row, 0).text())

        # 查找原始记录
        original_record = None
        for record in self.pm.current_data["tables"][self.current_table]:
            if record["id"] == record_id:
                original_record = record
                break

        if not original_record:
            QMessageBox.critical(self, "错误", "未找到记录")
            return

        # 验证主密码
        dialog = SecurityDialog(self, "安全验证", "分享记录需要验证您的主密码")
        if dialog.exec_() == QDialog.Accepted:
            if self.pm.verify_master_password(dialog.get_password()):
                self.show_share_dialog(original_record)
            else:
                QMessageBox.warning(self, "验证失败", "主密码错误,无法分享记录")

    def show_share_dialog(self, record):
        """显示分享对话框"""
        dialog = ShareDialog(self, record)
        dialog.exec_()

    def manual_backup(self):
        """手动备份数据"""
        success, message = self.pm.backup_data("manual")
        if success:
            QMessageBox.information(self, "备份成功", message)
            self.update_status("数据备份完成")
        else:
            QMessageBox.critical(self, "备份失败", message)

    def change_password(self):
        """打开修改密码对话框"""
        dialog = ChangePasswordDialog(self, self.pm)
        result = dialog.exec_()

        if result == QDialog.Accepted:
            # 密码修改成功,可以执行一些后续操作
            self.update_status("密码修改成功")

            # 可选:询问用户是否重新登录
            reply = QMessageBox.question(self, "修改成功",
                                         "密码修改成功,建议重新登录以确保安全。\n是否立即重新登录?",
                                         QMessageBox.Yes | QMessageBox.No)

            if reply == QMessageBox.Yes:
                self.logout()

    # def change_password(self):
    #     """修改密码"""
    #     dialog = ChangePasswordDialog(self, self.pm)
    #     dialog.exec_()

    def logout(self):
        """注销当前账号"""
        reply = QMessageBox.question(self, "确认注销",
                                     "确定要注销当前账号吗?",
                                     QMessageBox.Yes | QMessageBox.No)

        if reply == QMessageBox.Yes:
            self.pm.logout()
            self.hide()
            self.login_window.show_login()

    def update_status(self, message):
        """更新状态栏"""
        self.status_label.setText(message)

    def excel_import(self):
        """打开Excel导入对话框"""
        dialog = ExcelImportDialog(self, self.pm)
        dialog.import_completed.connect(self.on_excel_import_completed)
        dialog.exec_()

    def on_excel_import_completed(self, success, message):
        """Excel导入完成回调"""
        if success:
            self.refresh_table()
            total_records = len(self.pm.current_data["tables"]["单位表"]) + len(self.pm.current_data["tables"]["个人表"])
            self.record_count_label.setText(f"总记录数: {total_records}")
        self.update_status(message)

    def export_data(self):
        """导出数据(Excel / CSV,导出内容为明文,须输入主密码授权,可勾选记录)"""
        data = self.pm.current_data.get("tables", {})
        dlg = ExportDialog(self, self.current_table, data)
        if dlg.exec_() != QDialog.Accepted:
            return
        fmt = dlg.get_choice()

        # 导出涉及明文密码,必须输入并验证主密码
        master_pwd = dlg.get_master_password()
        if not master_pwd:
            QMessageBox.warning(self, "需要主密码", "导出明文密码必须先输入主密码")
            return
        if not self.pm.verify_master_password(master_pwd):
            QMessageBox.warning(self, "验证失败", "主密码错误,已取消导出")
            return

        selected = dlg.get_selected()
        if not selected:
            QMessageBox.warning(self, "未选择记录", "请至少勾选一条要导出的记录")
            return

        scope = dlg.get_scope()
        if fmt == "excel":
            file_filter = "Excel 文件 (*.xlsx)"
            default_name = "墨香密码导出.xlsx"
        else:
            if scope == "all":
                file_filter = "ZIP 文件 (*.zip)"
                default_name = "墨香密码导出.zip"
            else:
                file_filter = "CSV 文件 (*.csv)"
                default_name = "墨香密码导出.csv"

        save_path, _ = QFileDialog.getSaveFileName(
            self, "导出数据", os.path.join(BASE_DIR, default_name), file_filter)
        if not save_path:
            return

        reply = QMessageBox.warning(
            self, "安全提醒",
            "导出的文件将包含【明文密码】。\n请勿通过微信/邮件等不安全渠道发送,也不要长期存放在公共电脑上。\n\n确定导出吗?",
            QMessageBox.Yes | QMessageBox.No)
        if reply != QMessageBox.Yes:
            return

        # 与导入格式对齐的列(去掉 id,导入时会重新分配)
        columns = ["网站名称", "网址", "账号", "密码", "备注"]
        try:
            # 按表分组勾选的记录
            by_table = {}
            for sel in selected:
                by_table.setdefault(sel["table"], []).append(sel["record"])

            if fmt == "excel":
                with pd.ExcelWriter(save_path, engine="openpyxl") as writer:
                    for t, rows in by_table.items():
                        df = pd.DataFrame(
                            [{c: r.get(c, "") for c in columns} for r in rows],
                            columns=columns)
                        df.to_excel(writer, sheet_name=t, index=False)
            else:
                if len(by_table) == 1:
                    # 单表:直接写 CSV,可被导入功能重新导入
                    rows = list(by_table.values())[0]
                    df = pd.DataFrame(
                        [{c: r.get(c, "") for c in columns} for r in rows],
                        columns=columns)
                    df.to_csv(save_path, index=False, encoding="utf-8-sig")
                else:
                    # 多表:打包成 zip,内含 单位表.csv / 个人表.csv,解压后可逐表导入
                    with zipfile.ZipFile(save_path, "w", zipfile.ZIP_DEFLATED) as zf:
                        for t, rows in by_table.items():
                            df = pd.DataFrame(
                                [{c: r.get(c, "") for c in columns} for r in rows],
                                columns=columns)
                            buffer = io.StringIO()
                            df.to_csv(buffer, index=False, encoding="utf-8-sig")
                            zf.writestr(f"{t}.csv", buffer.getvalue())

            QMessageBox.information(self, "导出成功", f"已导出 {len(selected)} 条记录到:\n{save_path}")
            self.update_status("数据导出完成")
        except Exception as e:
            QMessageBox.critical(self, "导出失败", f"导出数据时出错:{str(e)}")

    # 新增:重写关闭事件处理
    def closeEvent(self, event):
        """重写关闭事件,询问用户操作"""
        if getattr(self, '_relocking', False):
            event.accept()
            return
        if not self.app_controller.is_hidden_to_tray:
            reply = QMessageBox.question(
                self,
                "确认退出",
                "您确定要退出程序吗?\n\n选择'是'将退出程序,选择'否'将最小化到系统托盘。",
                QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel,
                QMessageBox.No
            )

            if reply == QMessageBox.Yes:
                # 退出程序
                event.accept()
                self.app_controller.quit_application()
            elif reply == QMessageBox.No:
                # 最小化到托盘
                event.ignore()
                self.app_controller.hide_to_tray()
            else:
                # 取消关闭
                event.ignore()
        else:
            event.accept()


def get_app_icon():
    """返回应用图标(优先 枫叶.ico,兼容打包环境),缺失时回退内置图标"""
    candidates = []
    if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
        candidates.append(os.path.join(sys._MEIPASS, '枫叶.ico'))
    candidates.append(os.path.join(BASE_DIR, '枫叶.ico'))
    candidates.append(os.path.join(BASE_DIR, '枫叶.png'))
    for path in candidates:
        if os.path.exists(path):
            icon = QIcon(path)
            if not icon.isNull():
                return icon
    from PyQt5.QtWidgets import QStyle
    return QApplication.style().standardIcon(QStyle.SP_DriveHDIcon)


class ApplicationController:
    """应用程序控制器,管理窗口切换"""

    def __init__(self):
        self.pm = PasswordManager()
        self.login_window = LoginWindow(self.pm)
        self.main_window = None
        self.tray_icon = None
        self.is_hidden_to_tray = False

        # 连接信号
        self.login_window.login_success.connect(self.show_main_window)

        # 初始化系统托盘
        self.init_tray_icon()

    def create_tray_icon(self):
        """创建托盘图标(统一使用 get_app_icon)"""
        return get_app_icon()

    def init_tray_icon(self):
        """初始化系统托盘图标"""
        if not QSystemTrayIcon.isSystemTrayAvailable():
            print("系统不支持托盘功能")
            return

        self.tray_icon = QSystemTrayIcon(self.login_window)

        # 设置托盘图标
        icon = self.create_tray_icon()
        self.tray_icon.setIcon(icon)
        self.tray_icon.setToolTip("墨香密码管理器")

        # 创建托盘菜单
        tray_menu = QMenu()

        show_action = QAction("显示主窗口", self.login_window)
        show_action.triggered.connect(self.show_main_window_from_tray)
        tray_menu.addAction(show_action)

        tray_menu.addSeparator()

        exit_action = QAction("退出", self.login_window)
        exit_action.triggered.connect(self.quit_application)
        tray_menu.addAction(exit_action)

        self.tray_icon.setContextMenu(tray_menu)
        self.tray_icon.activated.connect(self.on_tray_icon_activated)

        # 显示托盘图标
        self.tray_icon.show()
        print("系统托盘图标已初始化")

    def on_tray_icon_activated(self, reason):
        """托盘图标激活事件"""
        if reason == QSystemTrayIcon.DoubleClick:
            self.show_main_window_from_tray()
        elif reason == QSystemTrayIcon.Trigger:
            # 单击也可以显示菜单
            pass

    def show_main_window_from_tray(self):
        """从托盘显示主窗口"""
        if self.main_window:
            self.main_window.show()
            self.main_window.raise_()
            self.main_window.activateWindow()
            self.is_hidden_to_tray = False
            print("从托盘恢复主窗口")

    def show_main_window(self, username):
        """显示主窗口"""
        if self.main_window:
            self.main_window.close()

        self.main_window = MainWindow(self.pm, self.login_window, self)
        self.main_window.show()
        self.is_hidden_to_tray = False
        print("主窗口已显示")

    def hide_to_tray(self):
        """隐藏到系统托盘"""
        if self.main_window:
            self.main_window.hide()
            self.is_hidden_to_tray = True
            if self.tray_icon:
                self.tray_icon.showMessage(
                    "墨香密码管理器",
                    "程序已最小化到系统托盘",
                    QSystemTrayIcon.Information,
                    2000
                )
            print("已隐藏到系统托盘")

    def quit_application(self):
        """退出应用程序"""
        print("正在退出应用程序...")
        if self.tray_icon:
            self.tray_icon.hide()
        if self.main_window:
            self.main_window.close()
        if self.login_window:
            self.login_window.close()
        QApplication.quit()

    def start(self):
        """启动应用程序"""
        self.login_window.show_login()


def main():
    # 高 DPI 适配:必须在创建 QApplication 之前设置,避免 4K 屏模糊
    QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
    QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)

    # 修改:创建单实例应用程序
    app = SingleApplication(sys.argv)  # 使用 SingleApplication 替代 QApplication

    # 新增:检查是否已有实例运行
    if app.is_running():
        QMessageBox.information(
            None,
            "程序已运行",
            "墨香密码管理器已经在运行中!\n\n请检查系统托盘或任务栏。"
        )
        sys.exit(0)

    # 设置中国风样式
    ChineseStyle.setup_style(app)

    # 检查并安装必要的加密库
    try:
        from Crypto.Cipher import AES
    except ImportError:
        reply = QMessageBox.question(
            None, "缺少依赖库",
            "系统需要pycryptodome库进行数据加密。是否要安装?",
            QMessageBox.Yes | QMessageBox.No
        )
        if reply == QMessageBox.Yes:
            try:
                import subprocess
                subprocess.check_call([sys.executable, "-m", "pip", "install", "pycryptodome"])
                QMessageBox.information(None, "安装成功", "依赖库安装成功,请重新启动程序")
            except Exception as e:
                QMessageBox.critical(None, "安装失败", f"安装依赖库失败: {str(e)}")
            sys.exit(0)
        else:
            QMessageBox.warning(None, "警告", "未安装加密库,数据将不会加密存储")

    controller = ApplicationController()
    controller.start()

    sys.exit(app.exec_())


if __name__ == "__main__":
    main()


▌获取方式
1. 百度网盘 链接: https://pan.baidu.com/s/1S2DjUov0dfJwX2vUJNwGMA 提取码: byin
2. 夸克网盘 链接: https://pan.quark.cn/s/80928e23125e

免费评分

参与人数 3威望 +1 吾爱币 +22 热心值 +3 收起 理由
苏紫方璇 + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
melove210 + 1 + 1 谢谢@Thanks!
jiyu0418 + 1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

来自 2#
 楼主| phantomxjc 发表于 2026-8-25 11:46 |楼主
这么长篇大论一下是因为很多友友说这个东西不放心,so我把所有的源码都贴出来了,大家如果真想用,可以自行审查,如果不太懂编程,那么可以扔给ai问一下ai看看有没有安全隐患  哈哈 只想分享自己的东西,大家用还是不用取决于你们自己
3#
jjl 发表于 2026-8-25 11:47
4#
Notyu 发表于 2026-8-25 13:17
5#
woshizhugou 发表于 2026-8-25 13:25
感谢分享 厉害了楼主
6#
WAyh2026Xlbzn 发表于 2026-8-25 13:49
谢谢分享&#128591;&#127995;
7#
TaDxx 发表于 2026-8-25 16:09
为何还写Pyhton?
C#不更优秀吗?
8#
jmhh 发表于 2026-8-25 16:17
感谢博主
9#
p5jli777 发表于 2026-8-25 16:20
可以参考下Keepass
10#
sbjatqdx 发表于 2026-8-25 16:21
感谢你的分享,之前一直用keypass,不是它好久没更新了,试试你这款软件
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-9-10 05:02

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表