当前位置: 首页 > news >正文

Python和PyQt5写的密码记录工具

之前总是注册很多网址 依靠浏览器记住密码 或者随便写一个就忘了然后今天借助AI写了个密码管理器 记住密码 和所对应的网址 希望能帮到各位效果图 防止写入太多 增加了搜索 分页 以及超链接 可以直接点击网址就可以跳到对应的网站 右键 账号或者密码可以弹出复制选项 省的点击编辑再去复制了 样式不好看 但是能用 数据会保存到同级目录下有个 passwords.db 数据库
在这里插入图片描述

import sys
import sqlite3
from PyQt5.QtWidgets import (
    QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLineEdit, QPushButton, QTableWidget, QTableWidgetItem, QMessageBox, QDialog, QFormLayout, QHeaderView, QCheckBox, QAbstractItemView, QLabel, QComboBox, QMenu
)
from PyQt5.QtCore import Qt, QUrl
from PyQt5.QtGui import QDesktopServices, QColor, QClipboard

class AddPasswordDialog(QDialog):
    def __init__(self, parent=None, record=None):
        super().__init__(parent)
        self.setWindowTitle('添加/编辑记录')
        self.setModal(True)

        # 布局
        layout = QFormLayout(self)

        # 输入框
        self.website_input = QLineEdit(self)
        self.website_input.setPlaceholderText('网址')
        layout.addRow('网址:', self.website_input)

        self.username_input = QLineEdit(self)
        self.username_input.setPlaceholderText('用户名')
        layout.addRow('用户名:', self.username_input)

        self.password_input = QLineEdit(self)
        self.password_input.setPlaceholderText('密码')
        layout.addRow('密码:', self.password_input)

        self.notes_input = QLineEdit(self)
        self.notes_input.setPlaceholderText('备注')
        layout.addRow('备注:', self.notes_input)

        # 按钮
        buttons_layout = QHBoxLayout()
        save_button = QPushButton('保存', self)
        save_button.clicked.connect(self.accept)
        buttons_layout.addWidget(save_button)

        cancel_button = QPushButton('取消', self)
        cancel_button.clicked.connect(self.reject)
        buttons_layout.addWidget(cancel_button)

        layout.addRow(buttons_layout)

        # 如果是编辑模式,填充数据
        if record:
            self.website_input.setText(record[1])
            self.username_input.setText(record[2])
            self.password_input.setText(record[3])
            self.notes_input.setText(record[4])

    def get_data(self):
        return (
            self.website_input.text(),
            self.username_input.text(),
            self.password_input.text(),
            self.notes_input.text()
        )

class PasswordManager(QWidget):
    def __init__(self):
        super().__init__()
        self.current_page = 1  # 当前页
        self.page_size = 10  # 每页显示的记录数
        self.total_pages = 1  # 总页数
        self.initUI()

    def initUI(self):
        self.setWindowTitle('密码管理器')
        self.setGeometry(100, 100, 1000, 800)  # 设置窗口大小

        # 主布局
        main_layout = QVBoxLayout()

        # 顶部布局(搜索框和按钮)
        top_layout = QHBoxLayout()

        # 添加按钮(绿色)放在左上角
        add_button = QPushButton('添加', self)
        add_button.setStyleSheet('background-color: green; color: white; font-weight: bold; font-size: 18px; padding: 10px;')
        add_button.clicked.connect(self.show_add_dialog)
        top_layout.addWidget(add_button, alignment=Qt.AlignLeft)

        # 搜索框
        self.search_input = QLineEdit(self)
        self.search_input.setPlaceholderText('搜索(网址、用户名、备注)')
        self.search_input.textChanged.connect(self.search_passwords)
        top_layout.addWidget(self.search_input)

        # 删除按钮(红色)放在右上角
        delete_button = QPushButton('删除', self)
        delete_button.setStyleSheet('background-color: red; color: white; font-weight: bold; font-size: 18px; padding: 10px;')
        delete_button.clicked.connect(self.delete_password)
        top_layout.addWidget(delete_button, alignment=Qt.AlignRight)

        main_layout.addLayout(top_layout)

        # 密码表格
        self.password_table = QTableWidget(self)
        self.password_table.setColumnCount(6)  # 添加一列用于选择框
        self.password_table.setHorizontalHeaderLabels(['选择', '网站', '用户名', '密码', '备注', '操作'])
        self.password_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)  # 自适应列宽
        self.password_table.setEditTriggers(QTableWidget.NoEditTriggers)  # 禁止编辑
        self.password_table.cellDoubleClicked.connect(self.open_website)  # 双击打开网址
        self.password_table.setSelectionBehavior(QAbstractItemView.SelectRows)  # 单击行即可选择
        self.password_table.setContextMenuPolicy(Qt.CustomContextMenu)  # 启用右键菜单
        self.password_table.customContextMenuRequested.connect(self.show_context_menu)  # 右键菜单事件
        main_layout.addWidget(self.password_table)

        # 底部布局(全选、分页符号、跳转页码、每页条数)
        bottom_layout = QHBoxLayout()

        # 全选框放在最左侧
        self.select_all_checkbox = QCheckBox('全选', self)
        self.select_all_checkbox.stateChanged.connect(self.select_all_checkbox_changed)
        bottom_layout.addWidget(self.select_all_checkbox, alignment=Qt.AlignLeft)

        # 分页符号和页码
        pagination_layout = QHBoxLayout()

        # 上一页按钮
        prev_button = QPushButton('<', self)
        prev_button.setStyleSheet('font-size: 14px;')
        prev_button.clicked.connect(self.prev_page)
        pagination_layout.addWidget(prev_button)

        # 当前页码和总页数
        self.page_info_label = QLabel(f'第 {self.current_page} 页 / {self.total_pages} 页', self)
        self.page_info_label.setStyleSheet('font-size: 16px; color: #333;')
        pagination_layout.addWidget(self.page_info_label)

        # 下一页按钮
        next_button = QPushButton('>', self)
        next_button.setStyleSheet('font-size: 14px;')
        next_button.clicked.connect(self.next_page)
        pagination_layout.addWidget(next_button)

        # 跳转到第几页
        self.jump_to_page_input = QLineEdit(self)
        self.jump_to_page_input.setPlaceholderText('跳转到第几页')
        self.jump_to_page_input.setFixedWidth(100)
        pagination_layout.addWidget(self.jump_to_page_input)

        jump_button = QPushButton('跳转', self)
        jump_button.setStyleSheet('font-size: 14px;')
        jump_button.clicked.connect(self.jump_to_page)
        pagination_layout.addWidget(jump_button)

        # 每页显示条数
        self.page_size_combo = QComboBox(self)
        self.page_size_combo.addItems(['10', '20', '50', '100'])
        self.page_size_combo.setCurrentText(str(self.page_size))
        self.page_size_combo.currentTextChanged.connect(self.change_page_size)
        pagination_layout.addWidget(QLabel('每页显示条数:', self))
        pagination_layout.addWidget(self.page_size_combo)

        bottom_layout.addLayout(pagination_layout)
        main_layout.addLayout(bottom_layout)

        # 设置主布局
        self.setLayout(main_layout)

        # 加载密码列表
        self.load_passwords()

    def show_add_dialog(self, record=None):
        dialog = AddPasswordDialog(self, record)
        if dialog.exec_() == QDialog.Accepted:
            website, username, password, notes = dialog.get_data()

            if website and username and password:
                conn = sqlite3.connect('passwords.db')
                c = conn.cursor()
                if record:
                    # 编辑模式
                    c.execute("UPDATE passwords SET website = ?, username = ?, password = ?, notes = ? WHERE id = ?",
                              (website, username, password, notes, record[0]))
                else:
                    # 添加模式
                    c.execute("SELECT id FROM passwords WHERE website = ?", (website,))
                    if c.fetchone():
                        QMessageBox.warning(self, '错误', '该网址已存在!')
                    else:
                        c.execute("INSERT INTO passwords (website, username, password, notes) VALUES (?, ?, ?, ?)",
                                  (website, username, password, notes))
                conn.commit()
                conn.close()

                self.load_passwords()
            else:
                QMessageBox.warning(self, '错误', '请填写网址、用户名和密码')

    def load_passwords(self, search_keyword=None):
        self.password_table.setRowCount(0)  # 清空表格

        # 计算偏移量
        offset = (self.current_page - 1) * self.page_size

        conn = sqlite3.connect('passwords.db')
        c = conn.cursor()
        if search_keyword:
            # 全局搜索,不限制当前页
            c.execute(f"SELECT id, website, username, password, notes FROM passwords WHERE website LIKE ? OR username LIKE ? OR notes LIKE ?",
                      (f"%{search_keyword}%", f"%{search_keyword}%", f"%{search_keyword}%"))
        else:
            # 分页查询
            c.execute(f"SELECT id, website, username, password, notes FROM passwords LIMIT ? OFFSET ?",
                      (self.page_size, offset))
        passwords = c.fetchall()

        # 获取总记录数,用于计算总页数
        if search_keyword:
            # 如果是搜索模式,总页数基于搜索结果
            self.total_pages = (len(passwords) + self.page_size - 1) // self.page_size
        else:
            # 如果不是搜索模式,总页数基于全部数据
            c.execute("SELECT COUNT(*) FROM passwords")
            total_count = c.fetchone()[0]
            self.total_pages = (total_count + self.page_size - 1) // self.page_size
        conn.close()

        for row, password in enumerate(passwords):
            self.password_table.insertRow(row)

            # 选择框列
            checkbox = QCheckBox(self)
            checkbox.setChecked(False)
            checkbox.setProperty("id", password[0])  # 使用 setProperty 存储ID
            self.password_table.setCellWidget(row, 0, checkbox)

            # 网站列(可点击超链接)
            website_item = QTableWidgetItem(password[1])
            website_item.setData(Qt.UserRole, password[1])  # 存储网址
            website_item.setForeground(QColor(0, 0, 255))  # 蓝色字体
            website_item.setFlags(website_item.flags() | Qt.ItemIsEnabled)  # 启用点击
            self.password_table.setItem(row, 1, website_item)

            self.password_table.setItem(row, 2, QTableWidgetItem(password[2]))
            self.password_table.setItem(row, 3, QTableWidgetItem(password[3]))
            self.password_table.setItem(row, 4, QTableWidgetItem(password[4]))

            # 操作列(编辑按钮)
            edit_button = QPushButton('编辑', self)
            edit_button.setStyleSheet('background-color: blue; color: white; font-weight: bold;')
            edit_button.clicked.connect(lambda _, record=password: self.show_add_dialog(record))
            self.password_table.setCellWidget(row, 5, edit_button)

        # 更新页码显示
        self.page_info_label.setText(f'第 {self.current_page} 页 / {self.total_pages} 页')

    def search_passwords(self):
        search_keyword = self.search_input.text()
        self.current_page = 1  # 搜索时重置到第一页
        self.load_passwords(search_keyword)

    def delete_password(self):
        selected_records = []
        for row in range(self.password_table.rowCount()):
            checkbox = self.password_table.cellWidget(row, 0)
            if checkbox.isChecked():
                # 获取选中行的信息
                website = self.password_table.item(row, 1).text()
                username = self.password_table.item(row, 2).text()
                notes = self.password_table.item(row, 4).text()
                selected_records.append(f"网站: {website}, 用户名: {username}, 备注: {notes}")

        if selected_records:
            confirmation = QMessageBox.question(self, '确认删除', f"确认删除以下记录:\n\n" + "\n".join(selected_records),
                                                QMessageBox.Yes | QMessageBox.No)
            if confirmation == QMessageBox.Yes:
                conn = sqlite3.connect('passwords.db')
                c = conn.cursor()
                for row in range(self.password_table.rowCount()):
                    checkbox = self.password_table.cellWidget(row, 0)
                    if checkbox.isChecked():
                        password_id = checkbox.property("id")
                        c.execute("DELETE FROM passwords WHERE id = ?", (password_id,))
                conn.commit()
                conn.close()

                self.load_passwords()
        else:
            QMessageBox.warning(self, '错误', '请选择一条或多条记录')

    def select_all_checkbox_changed(self, state):
        # 全选框改变时,设置所有行的选择框
        for row in range(self.password_table.rowCount()):
            checkbox = self.password_table.cellWidget(row, 0)
            checkbox.setChecked(state == Qt.Checked)

    def open_website(self, row, column):
        if column == 1:  # 只有网站列可以点击
            website = self.password_table.item(row, column).data(Qt.UserRole)
            if website:
                if not website.startswith("http://") and not website.startswith("https://"):
                    website = f"https://{website}"
                QDesktopServices.openUrl(QUrl(website))

    def prev_page(self):
        if self.current_page > 1:
            self.current_page -= 1
            self.load_passwords()

    def next_page(self):
        if self.current_page < self.total_pages:
            self.current_page += 1
            self.load_passwords()

    def jump_to_page(self):
        try:
            page = int(self.jump_to_page_input.text())
            if 1 <= page <= self.total_pages:
                self.current_page = page
                self.load_passwords()
            else:
                QMessageBox.warning(self, '错误', f'页码必须在 1 到 {self.total_pages} 之间!')
        except ValueError:
            QMessageBox.warning(self, '错误', '请输入有效的页码!')

    def change_page_size(self, size):
        self.page_size = int(size)
        self.current_page = 1  # 重置到第一页
        self.load_passwords()

    def show_context_menu(self, pos):
        # 获取当前选中的行和列
        row = self.password_table.rowAt(pos.y())
        col = self.password_table.columnAt(pos.x())

        # 只有用户名(列2)和密码(列3)支持右键复制
        if row >= 0 and col in [2, 3]:
            menu = QMenu(self)

            # 复制用户名
            if col == 2:
                copy_username_action = menu.addAction('复制用户名')
                copy_username_action.triggered.connect(lambda: self.copy_to_clipboard(row, 2))

            # 复制密码
            if col == 3:
                copy_password_action = menu.addAction('复制密码')
                copy_password_action.triggered.connect(lambda: self.copy_to_clipboard(row, 3))

            # 显示菜单
            menu.exec_(self.password_table.viewport().mapToGlobal(pos))

    def copy_to_clipboard(self, row, col):
        # 获取单元格内容
        item = self.password_table.item(row, col)
        if item:
            clipboard = QApplication.clipboard()
            clipboard.setText(item.text())
            QMessageBox.information(self, '复制成功', f'已复制到剪贴板:{item.text()}')

def create_database():
    conn = sqlite3.connect('passwords.db')
    c = conn.cursor()
    c.execute('''CREATE TABLE IF NOT EXISTS passwords
                 (id INTEGER PRIMARY KEY AUTOINCREMENT,
                  website TEXT NOT NULL,
                  username TEXT NOT NULL,
                  password TEXT NOT NULL,
                  notes TEXT)''')
    conn.commit()
    conn.close()

if __name__ == '__main__':
    create_database()
    app = QApplication(sys.argv)
    manager = PasswordManager()
    manager.show()
    sys.exit(app.exec_())

相关文章:

  • 三方库总结
  • 模块11_面向对象
  • NLP如何训练AI模型以理解知识
  • C# IComparable<T> 使用详解
  • Hi3516CV610电瓶车检测 电动自行车检测 人脸检测 人形检测 车辆检测 宠物检测 包裹检测 源码
  • MWC 2025 | 移远通信大模型解决方案加速落地,引领服务机器人创新变革
  • 嵌入式学习第二十三天--网络及TCP
  • AtCoder Beginner Contest 395 E
  • Python:类型转换和深浅拷贝,可变与不可变对象
  • etcd wal fsync延迟过高:影响范围、排查步骤、可能原因、处理方案
  • SparkStreaming之03:容错、语义、整合kafka、Exactly-Once、ScalikeJDBC
  • C++入门基础
  • 【三维生成】StarGen:基于视频扩散模型的可扩展的时空自回归场景生成
  • maven高级-04.继承与聚合-聚合实现
  • 行为模式---命令模式
  • 自动索引技术实操
  • ZCC5090EA适用于TYPE-C接口,集成30V OVP功能, 最大1.5A充电电流,带NTC及使能功能,双节锂电升压充电芯片替代CS5090EA
  • SQLite Alter 命令详解
  • 使用VSCode Debugger 调试 React项目
  • AutoGen学习笔记系列(二)Tutorial - Messages
  • 本机怎么放自己做的网站/如何建一个自己的网站
  • 门户网站建设存在问题与不足/建立免费个人网站
  • 平台网站怎么做的好/seo优化报告
  • 做网站的 视频/西安网站建设排名
  • 医疗知识普及网站开发/网络推广公司是干嘛的
  • 阿里云网站建设 部署与发布答案/徐州自动seo