create my toolbox -- first commit
This commit is contained in:
		
							
								
								
									
										0
									
								
								toolbox/codes/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								toolbox/codes/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										0
									
								
								toolbox/codes/app/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								toolbox/codes/app/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										0
									
								
								toolbox/codes/common/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								toolbox/codes/common/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										20
									
								
								toolbox/codes/common/clibs.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								toolbox/codes/common/clibs.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from threading import Lock
 | 
			
		||||
 | 
			
		||||
base_path = Path(__file__).resolve().parent.parent.parent
 | 
			
		||||
lock = Lock()
 | 
			
		||||
account = None
 | 
			
		||||
code_dict = [4, 11, 4, 31, 22, 12, 19, 23, 7, 16, 7, 23, 1, 8, 7, 18, 27, 32, 28, 25, 7, 32, 9, 15, 2, 32, 0, 12, 26, 15, 14, 17]
 | 
			
		||||
username, password = "", ""
 | 
			
		||||
avatar = f"{base_path}/assets/media/avatar.jpg"
 | 
			
		||||
proverb = "佛曰:Time will say~"
 | 
			
		||||
bg = f"{base_path}/assets/media/bg.jpg"
 | 
			
		||||
win_width, win_height = 1100, 500
 | 
			
		||||
def delete_files_in_directory(directory):
 | 
			
		||||
    path = Path(directory)
 | 
			
		||||
    if path.exists() and path.is_dir():
 | 
			
		||||
        for child in path.iterdir():
 | 
			
		||||
            if child.is_file():
 | 
			
		||||
                child.unlink()
 | 
			
		||||
            elif child.is_dir():
 | 
			
		||||
                delete_files_in_directory(child)
 | 
			
		||||
							
								
								
									
										80
									
								
								toolbox/codes/common/db_operation.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								toolbox/codes/common/db_operation.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,80 @@
 | 
			
		||||
import sqlite3
 | 
			
		||||
import time
 | 
			
		||||
from codes.common import clibs
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def db_init(db_file):
 | 
			
		||||
    conn = sqlite3.connect(db_file, isolation_level=None, check_same_thread=False, cached_statements=2048, timeout=10.0)
 | 
			
		||||
    cursor = conn.cursor()
 | 
			
		||||
    cursor.execute("PRAGMA journal_mode=wal")
 | 
			
		||||
    cursor.execute("PRAGMA wal_checkpoint=TRUNCATE")
 | 
			
		||||
    cursor.execute("PRAGMA synchronous=normal")
 | 
			
		||||
    cursor.execute("PRAGMA temp_store=memory")
 | 
			
		||||
    cursor.execute("PRAGMA mmap_size=30000000000")
 | 
			
		||||
    cursor.execute("PRAGMA cache_size=200000")
 | 
			
		||||
    cursor.execute(
 | 
			
		||||
        """
 | 
			
		||||
        create table if not exists logs(
 | 
			
		||||
            id integer primary key autoincrement,
 | 
			
		||||
            timestamp DATETIME DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW', 'localtime')),
 | 
			
		||||
            level text,
 | 
			
		||||
            module text,
 | 
			
		||||
            content text
 | 
			
		||||
        )
 | 
			
		||||
        """
 | 
			
		||||
    )
 | 
			
		||||
    cursor.execute(
 | 
			
		||||
        """
 | 
			
		||||
        create table if not exists users(
 | 
			
		||||
            id integer primary key autoincrement,
 | 
			
		||||
            timestamp DATETIME DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW', 'localtime')),
 | 
			
		||||
            username text not null unique,
 | 
			
		||||
            password text not null,
 | 
			
		||||
            salt text not null
 | 
			
		||||
        )
 | 
			
		||||
        """
 | 
			
		||||
    )
 | 
			
		||||
    cursor.close()
 | 
			
		||||
    conn.close()
 | 
			
		||||
 | 
			
		||||
def db_lock(func):
 | 
			
		||||
    def wrapper(*args, **kwargs):
 | 
			
		||||
        try:
 | 
			
		||||
            clibs.lock.acquire(True)
 | 
			
		||||
            ret = func(*args, **kwargs)
 | 
			
		||||
        finally:
 | 
			
		||||
            clibs.lock.release()
 | 
			
		||||
        return ret
 | 
			
		||||
    return wrapper
 | 
			
		||||
 | 
			
		||||
def db_backup():
 | 
			
		||||
    t = time.strftime("%Y%m%d%H%M%S", time.localtime())
 | 
			
		||||
    db_file = clibs.base_path / "assets/database/toolbox.db"
 | 
			
		||||
    db_file_backup = clibs.base_path / f"assets/database/toolbox.{t}.db"
 | 
			
		||||
    if not (db_file.exists() and db_file.is_file()):
 | 
			
		||||
        db_init(db_file)
 | 
			
		||||
    else:
 | 
			
		||||
        db_file_backup.write_bytes(db_file.read_bytes())
 | 
			
		||||
        db_dir = clibs.base_path / "assets/database"
 | 
			
		||||
        db_list = [db for db in db_dir.glob("*.db")]
 | 
			
		||||
        for db in sorted(db_list)[:-clibs.account["maximum_db_number"]]:
 | 
			
		||||
            db.unlink()
 | 
			
		||||
 | 
			
		||||
def db_conn():
 | 
			
		||||
    db_file = clibs.base_path / "assets/database/toolbox.db"
 | 
			
		||||
    conn = sqlite3.connect(db_file, isolation_level=None, check_same_thread=False, cached_statements=2048, timeout=3.0)
 | 
			
		||||
    cursor = conn.cursor()
 | 
			
		||||
    cursor.execute("PRAGMA journal_mode=wal")
 | 
			
		||||
    cursor.execute("PRAGMA wal_checkpoint=TRUNCATE")
 | 
			
		||||
    cursor.execute("PRAGMA synchronous=normal")
 | 
			
		||||
    cursor.execute("PRAGMA temp_store=memory")
 | 
			
		||||
    cursor.execute("PRAGMA mmap_size=30000000000")
 | 
			
		||||
    cursor.execute("PRAGMA cache_size=200000")
 | 
			
		||||
    return conn, cursor
 | 
			
		||||
 | 
			
		||||
@db_lock
 | 
			
		||||
def db_close(conn, cursor):
 | 
			
		||||
    cursor.close()
 | 
			
		||||
    conn.close()
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										37
									
								
								toolbox/codes/common/secure_encrypt.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								toolbox/codes/common/secure_encrypt.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
import base64
 | 
			
		||||
import hashlib
 | 
			
		||||
from Crypto.Cipher import AES
 | 
			
		||||
from Crypto import Random
 | 
			
		||||
from Crypto.Util.Padding import pad, unpad
 | 
			
		||||
from codes.common import clibs
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PassCipher:
 | 
			
		||||
    def __init__(self, salt):
 | 
			
		||||
        salt = salt.encode("utf-8")
 | 
			
		||||
        self.key = hashlib.sha256(salt).digest()
 | 
			
		||||
 | 
			
		||||
    def encrypt(self, plaintext):
 | 
			
		||||
        plaintext = plaintext.encode("utf-8")
 | 
			
		||||
        iv = Random.new().read(AES.block_size)
 | 
			
		||||
        cipher = AES.new(self.key, AES.MODE_CBC, iv)
 | 
			
		||||
        ciphertext = cipher.encrypt(pad(plaintext, AES.block_size))
 | 
			
		||||
        return base64.b64encode(iv + ciphertext).decode("utf-8")
 | 
			
		||||
 | 
			
		||||
    def decrypt(self, ciphertext):
 | 
			
		||||
        ciphertext = base64.b64decode(ciphertext)
 | 
			
		||||
        iv = ciphertext[:AES.block_size]
 | 
			
		||||
        ciphertext = ciphertext[AES.block_size:]
 | 
			
		||||
        cipher = AES.new(self.key, AES.MODE_CBC, iv)
 | 
			
		||||
        plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size)
 | 
			
		||||
        return plaintext.decode("utf-8")
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def gen_salt(text: str):
 | 
			
		||||
        key = ""
 | 
			
		||||
        passwd = {idx: char for idx, char in enumerate(text * 4)}
 | 
			
		||||
        for idx in range(32):
 | 
			
		||||
            char_i = 0 if ord(passwd[idx]) - clibs.code_dict[idx] < 0 else ord(passwd[idx]) - clibs.code_dict[idx]
 | 
			
		||||
            key += chr(char_i)
 | 
			
		||||
        salt = base64.urlsafe_b64encode(key.encode()).decode()
 | 
			
		||||
        return salt
 | 
			
		||||
							
								
								
									
										28
									
								
								toolbox/codes/common/ui2py.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								toolbox/codes/common/ui2py.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
import subprocess
 | 
			
		||||
import sys
 | 
			
		||||
from codes.common import clibs
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
 | 
			
		||||
UIC_CMD = "pyside6-uic"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def single_uic(ui_path: str, py_path: str):
 | 
			
		||||
    for file in Path(ui_path).rglob("*.ui"):
 | 
			
		||||
        file_name = file.stem
 | 
			
		||||
        ui_file = file
 | 
			
		||||
        py_file = Path(py_path) / f"{file_name}.py"
 | 
			
		||||
        cmd = [UIC_CMD, "-o", py_file, ui_file]
 | 
			
		||||
 | 
			
		||||
        print(f"Processing  {ui_file} -> {py_file}")
 | 
			
		||||
        try:
 | 
			
		||||
            subprocess.run(cmd, check=True, capture_output=True, text=True)
 | 
			
		||||
        except subprocess.CalledProcessError as e:
 | 
			
		||||
            print(f"转换失败: {ui_file}\n{e.stderr}", file=sys.stderr)
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
    ui_path = clibs.base_path / "assets" / "ui"
 | 
			
		||||
    py_path = clibs.base_path / "codes" / "ui"
 | 
			
		||||
    single_uic(str(ui_path), str(py_path))
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    main()
 | 
			
		||||
							
								
								
									
										40
									
								
								toolbox/codes/common/worker.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								toolbox/codes/common/worker.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
from PySide6.QtCore import QThread, Signal, QRunnable, QThreadPool, QMetaObject, Q_ARG, QMetaType, Qt
 | 
			
		||||
from typing import Callable, Any
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Worker(QThread):
 | 
			
		||||
    result = Signal(dict)
 | 
			
		||||
    error = Signal(dict)
 | 
			
		||||
 | 
			
		||||
    def __init__(self, func, *args, **kwargs):
 | 
			
		||||
        super().__init__()
 | 
			
		||||
        self.func = func
 | 
			
		||||
        self.args = args
 | 
			
		||||
        self.kwargs = kwargs
 | 
			
		||||
 | 
			
		||||
    def run(self):
 | 
			
		||||
        try:
 | 
			
		||||
            result = self.func(*self.args, **self.kwargs)
 | 
			
		||||
            self.result.emit({"result": result})
 | 
			
		||||
        except Exception as error:
 | 
			
		||||
            self.error.emit({"error": str(error)})
 | 
			
		||||
 | 
			
		||||
# launch函数必须在主进程中定义调用
 | 
			
		||||
# def launch(self, func, on_anything: Callable[..., Any] = print, *args, **kwargs):
 | 
			
		||||
#     self.thread = Worker(func, *args, **kwargs)
 | 
			
		||||
#     self.thread.started.connect(lambda: on_anything({"started": True}))
 | 
			
		||||
#     self.thread.result.connect(on_anything)
 | 
			
		||||
#     self.thread.error.connect(on_anything)
 | 
			
		||||
#     self.thread.finished.connect(lambda: on_anything({"finished": True}))
 | 
			
		||||
#     self.thread.start()
 | 
			
		||||
#
 | 
			
		||||
#
 | 
			
		||||
# def on_anything(results):
 | 
			
		||||
#     if "started" in results:
 | 
			
		||||
#         print("运行开始:", results["started"])
 | 
			
		||||
#     if "result" in results:
 | 
			
		||||
#         print("正常结束:", results["result"])
 | 
			
		||||
#     if "error" in results:
 | 
			
		||||
#         print(f"有异常发生:", results["error"])
 | 
			
		||||
#     if "finished" in results:
 | 
			
		||||
#         print("运行结束:", results["finished"])
 | 
			
		||||
							
								
								
									
										238
									
								
								toolbox/codes/ui/login_ui.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										238
									
								
								toolbox/codes/ui/login_ui.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,238 @@
 | 
			
		||||
import sys
 | 
			
		||||
from PySide6.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QTabWidget, QListWidget, QStackedWidget, QCheckBox, QSpinBox, QToolBox, QLineEdit, QTableWidget, QTreeWidget, QCalendarWidget, QMessageBox, QToolBar, QSizePolicy
 | 
			
		||||
from PySide6.QtCore import Qt, QTime, QSize, QRect,QEvent, QThread
 | 
			
		||||
from PySide6.QtGui import QCursor, QFont, QIcon, QImage, QPixmap, QShortcut
 | 
			
		||||
from codes.common import clibs, db_operation
 | 
			
		||||
from codes.common.secure_encrypt import PassCipher
 | 
			
		||||
from codes.ui import main_ui
 | 
			
		||||
 | 
			
		||||
class LoginWindow(QWidget):
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        super().__init__()
 | 
			
		||||
        self.init_ui()
 | 
			
		||||
        self.setup_slot()
 | 
			
		||||
        self.predos()
 | 
			
		||||
        self.le_username.setFocus()
 | 
			
		||||
 | 
			
		||||
    def init_ui(self):
 | 
			
		||||
        self.setMinimumSize(420, 200)
 | 
			
		||||
        self.setMaximumSize(500, 240)
 | 
			
		||||
        self.resize(480, 200)
 | 
			
		||||
        self.setWindowTitle("登录")
 | 
			
		||||
        self.setWindowIcon(QIcon(f"{clibs.base_path}/assets/media/icon.ico"))
 | 
			
		||||
        self.setFont(QFont("Consolas", 14))
 | 
			
		||||
 | 
			
		||||
        self.layout_outter = QHBoxLayout()
 | 
			
		||||
        self.lb_logo = QLabel()
 | 
			
		||||
        self.lb_logo.setAlignment(Qt.AlignmentFlag.AlignCenter)
 | 
			
		||||
        self.lb_logo.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred)
 | 
			
		||||
        self.lb_logo.setPixmap(QPixmap(f"{clibs.base_path}/assets/media/logo.png"))
 | 
			
		||||
        self.lb_logo.setFixedSize(QSize(120, 120))
 | 
			
		||||
        self.lb_logo.setScaledContents(True)
 | 
			
		||||
        self.layout_outter.addWidget(self.lb_logo)
 | 
			
		||||
 | 
			
		||||
        self.tabW_login = QTabWidget()
 | 
			
		||||
        self.tab_login = QWidget()
 | 
			
		||||
        self.tabW_login.addTab(self.tab_login, "登录")
 | 
			
		||||
        self.tab_register = QWidget()
 | 
			
		||||
        self.tabW_login.addTab(self.tab_register, "注册")
 | 
			
		||||
        self.layout_outter.addWidget(self.tabW_login)
 | 
			
		||||
 | 
			
		||||
        # 登陆页面
 | 
			
		||||
        self.layout_H_username = QHBoxLayout()
 | 
			
		||||
        self.lb_username = QLabel("账号")
 | 
			
		||||
        self.lb_username.setAlignment(Qt.AlignmentFlag.AlignCenter)
 | 
			
		||||
        self.layout_H_username.addWidget(self.lb_username)
 | 
			
		||||
        self.le_username = QLineEdit()
 | 
			
		||||
        self.le_username.setFocus()
 | 
			
		||||
        self.layout_H_username.addWidget(self.le_username)
 | 
			
		||||
 | 
			
		||||
        self.layout_H_password = QHBoxLayout()
 | 
			
		||||
        self.lb_password = QLabel("密码")
 | 
			
		||||
        self.lb_password.setAlignment(Qt.AlignmentFlag.AlignCenter)
 | 
			
		||||
        self.layout_H_password.addWidget(self.lb_password)
 | 
			
		||||
        self.le_password = QLineEdit()
 | 
			
		||||
        self.le_password.setEchoMode(QLineEdit.EchoMode.Password)
 | 
			
		||||
        self.layout_H_password.addWidget(self.le_password)
 | 
			
		||||
 | 
			
		||||
        self.layout_H_button = QHBoxLayout()
 | 
			
		||||
        self.btn_login = QPushButton("登录")
 | 
			
		||||
        self.btn_login.setAutoDefault(True)
 | 
			
		||||
        self.btn_cancel = QPushButton("取消")
 | 
			
		||||
        self.btn_cancel.setAutoDefault(True)
 | 
			
		||||
        self.layout_H_button.addWidget(self.btn_login)
 | 
			
		||||
        self.layout_H_button.addWidget(self.btn_cancel)
 | 
			
		||||
 | 
			
		||||
        self.layout_V_user_pass = QVBoxLayout()
 | 
			
		||||
        self.layout_V_user_pass.addLayout(self.layout_H_username)
 | 
			
		||||
        self.layout_V_user_pass.addLayout(self.layout_H_password)
 | 
			
		||||
        self.layout_V_user_pass.addLayout(self.layout_H_button)
 | 
			
		||||
        self.tab_login.setLayout(self.layout_V_user_pass)
 | 
			
		||||
 | 
			
		||||
        # 注册页面
 | 
			
		||||
        self.layout_H_username_reg = QHBoxLayout()
 | 
			
		||||
        self.lb_username_reg = QLabel("账号设定")
 | 
			
		||||
        self.lb_username_reg.setAlignment(Qt.AlignmentFlag.AlignCenter)
 | 
			
		||||
        self.layout_H_username_reg.addWidget(self.lb_username_reg)
 | 
			
		||||
        self.le_username_reg = QLineEdit()
 | 
			
		||||
        self.le_username_reg.setFocus()
 | 
			
		||||
        self.layout_H_username_reg.addWidget(self.le_username_reg)
 | 
			
		||||
 | 
			
		||||
        self.layout_H_password_reg = QHBoxLayout()
 | 
			
		||||
        self.lb_password_reg = QLabel("密码设定")
 | 
			
		||||
        self.lb_password_reg.setAlignment(Qt.AlignmentFlag.AlignCenter)
 | 
			
		||||
        self.layout_H_password_reg.addWidget(self.lb_password_reg)
 | 
			
		||||
        self.le_password_reg = QLineEdit()
 | 
			
		||||
        self.le_password_reg.setEchoMode(QLineEdit.EchoMode.Password)
 | 
			
		||||
        self.layout_H_password_reg.addWidget(self.le_password_reg)
 | 
			
		||||
 | 
			
		||||
        self.layout_H_password_reg_confirm = QHBoxLayout()
 | 
			
		||||
        self.lb_password_reg_confirm = QLabel("密码确认")
 | 
			
		||||
        self.lb_password_reg_confirm.setAlignment(Qt.AlignmentFlag.AlignCenter)
 | 
			
		||||
        self.layout_H_password_reg_confirm.addWidget(self.lb_password_reg_confirm)
 | 
			
		||||
        self.le_password_reg_confirm = QLineEdit()
 | 
			
		||||
        self.le_password_reg_confirm.setEchoMode(QLineEdit.EchoMode.Password)
 | 
			
		||||
        self.layout_H_password_reg_confirm.addWidget(self.le_password_reg_confirm)
 | 
			
		||||
 | 
			
		||||
        self.layout_H_button_reg = QHBoxLayout()
 | 
			
		||||
        self.btn_login_reg = QPushButton("确认")
 | 
			
		||||
        self.btn_login_reg.setAutoDefault(True)
 | 
			
		||||
        self.btn_cancel_reg = QPushButton("取消")
 | 
			
		||||
        self.btn_cancel_reg.setAutoDefault(True)
 | 
			
		||||
        self.layout_H_button_reg.addWidget(self.btn_login_reg)
 | 
			
		||||
        self.layout_H_button_reg.addWidget(self.btn_cancel_reg)
 | 
			
		||||
 | 
			
		||||
        self.layout_V_user_pass_reg = QVBoxLayout()
 | 
			
		||||
        self.layout_V_user_pass_reg.addLayout(self.layout_H_username_reg)
 | 
			
		||||
        self.layout_V_user_pass_reg.addLayout(self.layout_H_password_reg)
 | 
			
		||||
        self.layout_V_user_pass_reg.addLayout(self.layout_H_password_reg_confirm)
 | 
			
		||||
        self.layout_V_user_pass_reg.addLayout(self.layout_H_button_reg)
 | 
			
		||||
        self.tab_register.setLayout(self.layout_V_user_pass_reg)
 | 
			
		||||
 | 
			
		||||
        self.setLayout(self.layout_outter)
 | 
			
		||||
 | 
			
		||||
    def setup_slot(self):
 | 
			
		||||
        self.tabW_login.currentChanged.connect(self.onChange_tabW)
 | 
			
		||||
        self.btn_login.clicked.connect(self.login_check)
 | 
			
		||||
        self.btn_cancel.clicked.connect(self.close)
 | 
			
		||||
        self.btn_login_reg.clicked.connect(self.register_check)
 | 
			
		||||
        self.btn_cancel_reg.clicked.connect(self.close)
 | 
			
		||||
        QShortcut("Esc", self).activated.connect(self.close)
 | 
			
		||||
        self.le_password.returnPressed.connect(self.login_check)
 | 
			
		||||
        self.le_password_reg_confirm.returnPressed.connect(self.register_check)
 | 
			
		||||
 | 
			
		||||
    def predos(self):
 | 
			
		||||
        db_file = clibs.base_path / "assets/database/toolbox.db"
 | 
			
		||||
        if not db_file.exists():
 | 
			
		||||
            db_operation.db_init(db_file)
 | 
			
		||||
 | 
			
		||||
    def onChange_tabW(self):
 | 
			
		||||
        text = self.tabW_login.tabText(self.tabW_login.currentIndex())
 | 
			
		||||
        if text == "登录":
 | 
			
		||||
            self.le_username.clear()
 | 
			
		||||
            self.le_password.clear()
 | 
			
		||||
            self.le_username.setFocus()
 | 
			
		||||
        elif text == "注册":
 | 
			
		||||
            self.le_username_reg.clear()
 | 
			
		||||
            self.le_password_reg.clear()
 | 
			
		||||
            self.le_password_reg_confirm.clear()
 | 
			
		||||
            self.le_username_reg.setFocus()
 | 
			
		||||
        else:
 | 
			
		||||
            raise Exception(f"Unknown TabWidget Name: {text}")
 | 
			
		||||
 | 
			
		||||
    def login_check(self):
 | 
			
		||||
        def login_failed():
 | 
			
		||||
            self.le_username.clear()
 | 
			
		||||
            self.le_password.clear()
 | 
			
		||||
            self.le_username.setFocus()
 | 
			
		||||
            QMessageBox.critical(self, "错误", "账号或密码错误,请重新输入!")
 | 
			
		||||
 | 
			
		||||
        def validate_login():
 | 
			
		||||
            nonlocal username, password
 | 
			
		||||
            conn, cursor = db_operation.db_conn()
 | 
			
		||||
            cursor.execute(f""" SELECT * FROM users where username = "{username}" """)
 | 
			
		||||
            record = cursor.fetchall()
 | 
			
		||||
            if len(record) == 0:
 | 
			
		||||
                login_failed()
 | 
			
		||||
            elif len(record) == 1:
 | 
			
		||||
                keys = ["id", "timestamp", "username", "password", "salt"]
 | 
			
		||||
                login_info = dict(zip(keys, record[0]))
 | 
			
		||||
                salt = PassCipher.gen_salt("@".join([username, password]))
 | 
			
		||||
                cipher = PassCipher(salt)
 | 
			
		||||
                # password_encrypt = cipher.encrypt("@".join([username, password]))
 | 
			
		||||
                # print(f"password_encrypt = {password_encrypt}")
 | 
			
		||||
                # exit()
 | 
			
		||||
                try:
 | 
			
		||||
                    decrypt_password = cipher.decrypt(login_info["password"])
 | 
			
		||||
                    if password != decrypt_password:
 | 
			
		||||
                        login_failed()
 | 
			
		||||
                        return False
 | 
			
		||||
                    else:
 | 
			
		||||
                        self.mainWindow = main_ui.MainWindow()
 | 
			
		||||
                        self.mainWindow.show()
 | 
			
		||||
                        db_operation.db_close(conn, cursor)
 | 
			
		||||
                        clibs.username = username
 | 
			
		||||
                        clibs.password = password
 | 
			
		||||
                        self.close()
 | 
			
		||||
                        return True
 | 
			
		||||
                except ValueError:
 | 
			
		||||
                    login_failed()
 | 
			
		||||
                    return False
 | 
			
		||||
            else:
 | 
			
		||||
                raise Exception(f"username duplicated: {username}")
 | 
			
		||||
 | 
			
		||||
        username = self.le_username.text()
 | 
			
		||||
        password = self.le_password.text()
 | 
			
		||||
 | 
			
		||||
        validate_login()
 | 
			
		||||
 | 
			
		||||
    def register_check(self):
 | 
			
		||||
        def register_failed(flag: int = 0):
 | 
			
		||||
            self.le_username_reg.clear()
 | 
			
		||||
            self.le_password_reg.clear()
 | 
			
		||||
            self.le_password_reg_confirm.clear()
 | 
			
		||||
            self.le_username_reg.setFocus()
 | 
			
		||||
            if flag == 0:
 | 
			
		||||
                QMessageBox.critical(self, "错误", "账号已存在,或两次输入密码不一致,请重新输入!")
 | 
			
		||||
            elif flag == 1:
 | 
			
		||||
                QMessageBox.critical(self, "错误", "密码长度不符合要求,请重新输入!")
 | 
			
		||||
 | 
			
		||||
        def validate_register():
 | 
			
		||||
            nonlocal username, password, password_confirm, record, conn, cursor
 | 
			
		||||
            if password != password_confirm:
 | 
			
		||||
                register_failed()
 | 
			
		||||
                return False
 | 
			
		||||
 | 
			
		||||
            if len(password) < clibs.account["minimum_password_length"]:
 | 
			
		||||
                register_failed(flag=1)
 | 
			
		||||
                return False
 | 
			
		||||
 | 
			
		||||
            if len(record) == 0:
 | 
			
		||||
                salt = PassCipher.gen_salt("@".join([username, password]))
 | 
			
		||||
                cipher = PassCipher(salt)
 | 
			
		||||
                password_encrypted = cipher.encrypt(password)
 | 
			
		||||
                cursor.execute("INSERT INTO users (username, password, salt) VALUES (?, ?, ?)", (username, password_encrypted, salt))
 | 
			
		||||
                QMessageBox.information(self, "成功", "注册成功,切换至登录窗口进行登录!")
 | 
			
		||||
                self.tabW_login.setCurrentIndex(self.tabW_login.indexOf(self.tab_login))
 | 
			
		||||
                return True
 | 
			
		||||
            elif len(record) == 1:
 | 
			
		||||
                register_failed()
 | 
			
		||||
                return False
 | 
			
		||||
            else:
 | 
			
		||||
                raise Exception(f"username duplicated: {username}")
 | 
			
		||||
 | 
			
		||||
        username = self.le_username_reg.text()
 | 
			
		||||
        password = self.le_password_reg.text()
 | 
			
		||||
        password_confirm = self.le_password_reg_confirm.text()
 | 
			
		||||
        conn, cursor = db_operation.db_conn()
 | 
			
		||||
        cursor.execute(f""" SELECT * FROM users where username = "{username}" """)
 | 
			
		||||
        record = cursor.fetchall()
 | 
			
		||||
        validate_register()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    app = QApplication(sys.argv)
 | 
			
		||||
    window = LoginWindow()
 | 
			
		||||
    window.show()
 | 
			
		||||
    sys.exit(app.exec())
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										208
									
								
								toolbox/codes/ui/main_ui.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										208
									
								
								toolbox/codes/ui/main_ui.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,208 @@
 | 
			
		||||
import json
 | 
			
		||||
from math import lgamma
 | 
			
		||||
from shutil import copy
 | 
			
		||||
from random import choice
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
import sys
 | 
			
		||||
import requests
 | 
			
		||||
from PySide6.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QTabWidget, QListWidget, QStackedWidget, QCheckBox, QSpinBox, QToolBox, QLineEdit, QTableWidget, QTreeWidget, QCalendarWidget, QMessageBox, QToolBar, QSizePolicy, QMainWindow, QStatusBar
 | 
			
		||||
from PySide6.QtCore import Qt, QTime, QSize, QRect,QEvent, QThread
 | 
			
		||||
from PySide6.QtGui import QCursor, QFont, QIcon, QImage, QPixmap, QShortcut, QAction, QKeySequence, QResizeEvent
 | 
			
		||||
from codes.common import clibs, db_operation
 | 
			
		||||
from codes.ui.widget_bg_ui import WidgetWithBg
 | 
			
		||||
from codes.common.worker import Worker
 | 
			
		||||
from typing import Callable, Any
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MainWindow(QMainWindow):
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        super().__init__()
 | 
			
		||||
        self.init_ui()
 | 
			
		||||
        self.setup_slot()
 | 
			
		||||
        self.predos()
 | 
			
		||||
 | 
			
		||||
    def init_ui(self):
 | 
			
		||||
        self.setMinimumSize(clibs.win_width, clibs.win_height)
 | 
			
		||||
        self.resize(clibs.win_width, clibs.win_height)
 | 
			
		||||
        self.setWindowTitle("Toolbox")
 | 
			
		||||
        self.setWindowIcon(QIcon(f"{clibs.base_path}/assets/media/icon.ico"))
 | 
			
		||||
        self.setFont(QFont("Consolas", 14))
 | 
			
		||||
        # 任务栏/主窗口/状态栏
 | 
			
		||||
        self.toolBar = QToolBar()
 | 
			
		||||
        self.addToolBar(self.toolBar)
 | 
			
		||||
        self.toolBar.setMovable(False)
 | 
			
		||||
        self.centralW = QWidget()
 | 
			
		||||
        self.setCentralWidget(self.centralW)
 | 
			
		||||
        self.statusBar = QStatusBar()
 | 
			
		||||
        self.setStatusBar(self.statusBar)
 | 
			
		||||
 | 
			
		||||
        # toolbar
 | 
			
		||||
        self.ac_homepage = QAction()
 | 
			
		||||
        self.ac_homepage.setMenuRole(QAction.MenuRole.NoRole)
 | 
			
		||||
        self.ac_homepage.setStatusTip("Go to homepage")
 | 
			
		||||
        self.ac_homepage.setToolTip("Ctrl+Alt+H")
 | 
			
		||||
        self.ac_homepage.setText("主页")
 | 
			
		||||
        self.ac_homepage.setShortcut(QKeySequence("Ctrl+Alt+H"))
 | 
			
		||||
        self.toolBar.addAction(self.ac_homepage)
 | 
			
		||||
 | 
			
		||||
    def setup_slot(self):
 | 
			
		||||
        self.ac_homepage.triggered.connect(self.ac_hp)
 | 
			
		||||
        # QShortcut("Esc", self).activated.connect(self.close)
 | 
			
		||||
 | 
			
		||||
    def predos(self):
 | 
			
		||||
        self.home_overlay = None
 | 
			
		||||
        db_operation.db_backup()
 | 
			
		||||
        self.conn, self.cursor = db_operation.db_conn()
 | 
			
		||||
 | 
			
		||||
    def ac_hp(self):
 | 
			
		||||
        def get_files(dir_path):
 | 
			
		||||
            folder = Path(dir_path)
 | 
			
		||||
            files = [p for p in folder.rglob("*") if p.is_file()]
 | 
			
		||||
            return choice(files), files
 | 
			
		||||
 | 
			
		||||
        def del_repeat_proverb(proverbs: list):
 | 
			
		||||
            _proverbs = []
 | 
			
		||||
            for proverb in proverbs:
 | 
			
		||||
                if proverb not in _proverbs:
 | 
			
		||||
                    _proverbs.append(proverb)
 | 
			
		||||
            return _proverbs
 | 
			
		||||
 | 
			
		||||
        def get_resources():
 | 
			
		||||
            # background image
 | 
			
		||||
            bing = "https://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=8"
 | 
			
		||||
            req = requests.get(bing)
 | 
			
		||||
            res = req.json()
 | 
			
		||||
            _, files = get_files(f"{clibs.base_path}/assets/media/bg")
 | 
			
		||||
            image_names = []
 | 
			
		||||
            for file in files:
 | 
			
		||||
                image_names.append(file.name.removesuffix(".jpg"))
 | 
			
		||||
            for image in res["images"]:
 | 
			
		||||
                startdate = image["startdate"]
 | 
			
		||||
                if startdate in image_names:
 | 
			
		||||
                    continue
 | 
			
		||||
                else:
 | 
			
		||||
                    image_url = f"""https://www.bing.com{image["url"]}"""
 | 
			
		||||
                    file = Path(f"{clibs.base_path}/assets/media/bg/{startdate}.jpg")
 | 
			
		||||
                    try:
 | 
			
		||||
                        req = requests.get(image_url, stream=True, timeout=10)
 | 
			
		||||
                        with open(file, "wb") as f:
 | 
			
		||||
                            for chunk in req.iter_content(chunk_size=8192):
 | 
			
		||||
                                f.write(chunk)
 | 
			
		||||
                    except Exception as e:
 | 
			
		||||
                        pass
 | 
			
		||||
            # proverbs
 | 
			
		||||
            hitokoto = "https://v1.hitokoto.cn/"
 | 
			
		||||
            proverbs = []
 | 
			
		||||
            try:
 | 
			
		||||
                req = requests.get(hitokoto)
 | 
			
		||||
                with open(f"{clibs.base_path}/assets/media/hitokoto.json", mode="rt", encoding="utf-8") as f:
 | 
			
		||||
                    proverbs = json.load(f)
 | 
			
		||||
                    proverbs.append(eval(req.text))
 | 
			
		||||
                    proverbs = del_repeat_proverb(proverbs)
 | 
			
		||||
                with open(f"{clibs.base_path}/assets/media/hitokoto.json", mode="wt", encoding="utf-8") as f:
 | 
			
		||||
                    json.dump(proverbs, f, ensure_ascii=False)
 | 
			
		||||
            except Exception as e:
 | 
			
		||||
                pass
 | 
			
		||||
 | 
			
		||||
        def change_resources():
 | 
			
		||||
            # avatar
 | 
			
		||||
            src, _ = get_files(f"{clibs.base_path}/assets/media/avatar")
 | 
			
		||||
            dst = f"{clibs.base_path}/assets/media/avatar.jpg"
 | 
			
		||||
            copy(src, dst)
 | 
			
		||||
            # proverbs
 | 
			
		||||
            with open(f"{clibs.base_path}/assets/media/hitokoto.json", mode="r", encoding="utf-8") as f:
 | 
			
		||||
                proverbs = json.load(f)
 | 
			
		||||
                res = choice(proverbs)
 | 
			
		||||
                sentence = res["hitokoto"]
 | 
			
		||||
                from_where = res["from"]
 | 
			
		||||
                from_who = res["from_who"]
 | 
			
		||||
                clibs.proverb = f"{sentence}\t\t※⌈{from_where}⌋ | {from_who}※"
 | 
			
		||||
            # bg
 | 
			
		||||
            src, _ = get_files(f"{clibs.base_path}/assets/media/bg")
 | 
			
		||||
            dst = f"{clibs.base_path}/assets/media/bg.jpg"
 | 
			
		||||
            copy(src, dst)
 | 
			
		||||
 | 
			
		||||
        def gen_page():
 | 
			
		||||
            self.set_shortcuts(False)
 | 
			
		||||
            self.home_overlay = WidgetWithBg(parent=self)
 | 
			
		||||
            self.home_overlay.on_closed.connect(self.exit_overlay)
 | 
			
		||||
            self.home_overlay.on_full_screen.connect(self.full_screen)
 | 
			
		||||
            self.home_overlay.show()
 | 
			
		||||
            width, height = self.width(), self.height()
 | 
			
		||||
            if width > clibs.win_width:
 | 
			
		||||
                self.resize(self.width()-1, self.height()-1)
 | 
			
		||||
            else:
 | 
			
		||||
                self.resize(clibs.win_width+1, clibs.win_height+1)
 | 
			
		||||
 | 
			
		||||
        change_resources()
 | 
			
		||||
        self.launch_get_resources(get_resources)
 | 
			
		||||
        gen_page()
 | 
			
		||||
 | 
			
		||||
    def full_screen(self, flag: bool):
 | 
			
		||||
        if flag == 0:
 | 
			
		||||
            if self.isFullScreen():
 | 
			
		||||
                self.setWindowFlags(self.windowFlags() ^ Qt.WindowType.WindowStaysOnTopHint)
 | 
			
		||||
                self.show()
 | 
			
		||||
                self.showMaximized()
 | 
			
		||||
            else:
 | 
			
		||||
                self.setWindowFlags(Qt.WindowType.WindowStaysOnTopHint)
 | 
			
		||||
                self.showFullScreen()
 | 
			
		||||
        elif flag == 1:
 | 
			
		||||
            self.setWindowFlags(self.windowFlags() ^ Qt.WindowType.WindowStaysOnTopHint)
 | 
			
		||||
            self.show()
 | 
			
		||||
            self.showMaximized()
 | 
			
		||||
 | 
			
		||||
    def exit_overlay(self):
 | 
			
		||||
        self.set_shortcuts(True)
 | 
			
		||||
        if self.isFullScreen():
 | 
			
		||||
            self.setWindowFlags(self.windowFlags() ^ Qt.WindowType.WindowStaysOnTopHint)
 | 
			
		||||
            self.show()
 | 
			
		||||
            self.showMaximized()
 | 
			
		||||
 | 
			
		||||
    def set_shortcuts(self, stat: bool = True):
 | 
			
		||||
        if stat:
 | 
			
		||||
            self.ac_homepage.setShortcut(QKeySequence("Ctrl+Alt+H"))
 | 
			
		||||
            # self.ac_settings.setShortcut(QKeySequence("Ctrl+Alt+S"))
 | 
			
		||||
            # self.ac_logs.setShortcut(QKeySequence("Ctrl+Alt+L"))
 | 
			
		||||
            # self.ac_about.setShortcut(QKeySequence("Ctrl+Alt+A"))
 | 
			
		||||
            # self.ac_caging.setShortcut(QKeySequence("Ctrl+Alt+C"))
 | 
			
		||||
            # self.ac_quit.setShortcut(QKeySequence("Ctrl+Alt+Q"))
 | 
			
		||||
        else:
 | 
			
		||||
            self.ac_homepage.setShortcut(QKeySequence())
 | 
			
		||||
            # self.ac_settings.setShortcut(QKeySequence())
 | 
			
		||||
            # self.ac_logs.setShortcut(QKeySequence())
 | 
			
		||||
            # self.ac_about.setShortcut(QKeySequence())
 | 
			
		||||
            # self.ac_caging.setShortcut(QKeySequence())
 | 
			
		||||
            # self.ac_quit.setShortcut(QKeySequence())
 | 
			
		||||
 | 
			
		||||
    def launch_get_resources(self, func, on_anything: Callable[..., Any] = None, *args, **kwargs):
 | 
			
		||||
        self.td_get_resources = Worker(func, *args, **kwargs)
 | 
			
		||||
        self.td_get_resources.started.connect(lambda: None)
 | 
			
		||||
        self.td_get_resources.result.connect(lambda: None)
 | 
			
		||||
        self.td_get_resources.error.connect(lambda: None)
 | 
			
		||||
        self.td_get_resources.finished.connect(lambda: None)
 | 
			
		||||
        self.td_get_resources.start()
 | 
			
		||||
 | 
			
		||||
    def resizeEvent(self, event: QResizeEvent):
 | 
			
		||||
        super().resizeEvent(event)
 | 
			
		||||
        if self.home_overlay:
 | 
			
		||||
            self.home_overlay.setGeometry(self.rect())
 | 
			
		||||
 | 
			
		||||
    def closeEvent(self, event):
 | 
			
		||||
        if self.isFullScreen():
 | 
			
		||||
            event.ignore()
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        reply = QMessageBox.question(self, "退出", "\n程序可能在运行,确定要退出吗?")
 | 
			
		||||
        if reply == QMessageBox.StandardButton.Yes:
 | 
			
		||||
            db_operation.db_close(self.conn, self.cursor)
 | 
			
		||||
            event.accept()
 | 
			
		||||
        else:
 | 
			
		||||
            event.ignore()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    app = QApplication(sys.argv)
 | 
			
		||||
    window = MainWindow()
 | 
			
		||||
    window.show()
 | 
			
		||||
    sys.exit(app.exec())
 | 
			
		||||
							
								
								
									
										145
									
								
								toolbox/codes/ui/untitled.ui
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								toolbox/codes/ui/untitled.ui
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,145 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<ui version="4.0">
 | 
			
		||||
 <class>Form</class>
 | 
			
		||||
 <widget class="QWidget" name="Form">
 | 
			
		||||
  <property name="geometry">
 | 
			
		||||
   <rect>
 | 
			
		||||
    <x>0</x>
 | 
			
		||||
    <y>0</y>
 | 
			
		||||
    <width>602</width>
 | 
			
		||||
    <height>376</height>
 | 
			
		||||
   </rect>
 | 
			
		||||
  </property>
 | 
			
		||||
  <property name="windowTitle">
 | 
			
		||||
   <string>Form</string>
 | 
			
		||||
  </property>
 | 
			
		||||
  <layout class="QVBoxLayout" name="verticalLayout_2">
 | 
			
		||||
   <item>
 | 
			
		||||
    <layout class="QVBoxLayout" name="verticalLayout">
 | 
			
		||||
     <item>
 | 
			
		||||
      <widget class="QLabel" name="label">
 | 
			
		||||
       <property name="text">
 | 
			
		||||
        <string>TextLabel</string>
 | 
			
		||||
       </property>
 | 
			
		||||
      </widget>
 | 
			
		||||
     </item>
 | 
			
		||||
     <item>
 | 
			
		||||
      <widget class="QLabel" name="label_2">
 | 
			
		||||
       <property name="minimumSize">
 | 
			
		||||
        <size>
 | 
			
		||||
         <width>125</width>
 | 
			
		||||
         <height>125</height>
 | 
			
		||||
        </size>
 | 
			
		||||
       </property>
 | 
			
		||||
       <property name="maximumSize">
 | 
			
		||||
        <size>
 | 
			
		||||
         <width>125</width>
 | 
			
		||||
         <height>125</height>
 | 
			
		||||
        </size>
 | 
			
		||||
       </property>
 | 
			
		||||
       <property name="text">
 | 
			
		||||
        <string/>
 | 
			
		||||
       </property>
 | 
			
		||||
       <property name="pixmap">
 | 
			
		||||
        <pixmap>../../assets/media/avatar.png</pixmap>
 | 
			
		||||
       </property>
 | 
			
		||||
       <property name="scaledContents">
 | 
			
		||||
        <bool>true</bool>
 | 
			
		||||
       </property>
 | 
			
		||||
       <property name="alignment">
 | 
			
		||||
        <set>Qt::AlignmentFlag::AlignCenter</set>
 | 
			
		||||
       </property>
 | 
			
		||||
      </widget>
 | 
			
		||||
     </item>
 | 
			
		||||
     <item>
 | 
			
		||||
      <widget class="QLabel" name="label_3">
 | 
			
		||||
       <property name="text">
 | 
			
		||||
        <string>Manford Fan · Code Create Life</string>
 | 
			
		||||
       </property>
 | 
			
		||||
       <property name="alignment">
 | 
			
		||||
        <set>Qt::AlignmentFlag::AlignCenter</set>
 | 
			
		||||
       </property>
 | 
			
		||||
      </widget>
 | 
			
		||||
     </item>
 | 
			
		||||
     <item>
 | 
			
		||||
      <widget class="Line" name="line">
 | 
			
		||||
       <property name="minimumSize">
 | 
			
		||||
        <size>
 | 
			
		||||
         <width>400</width>
 | 
			
		||||
         <height>0</height>
 | 
			
		||||
        </size>
 | 
			
		||||
       </property>
 | 
			
		||||
       <property name="maximumSize">
 | 
			
		||||
        <size>
 | 
			
		||||
         <width>400</width>
 | 
			
		||||
         <height>16777215</height>
 | 
			
		||||
        </size>
 | 
			
		||||
       </property>
 | 
			
		||||
       <property name="layoutDirection">
 | 
			
		||||
        <enum>Qt::LayoutDirection::LeftToRight</enum>
 | 
			
		||||
       </property>
 | 
			
		||||
       <property name="autoFillBackground">
 | 
			
		||||
        <bool>false</bool>
 | 
			
		||||
       </property>
 | 
			
		||||
       <property name="frameShadow">
 | 
			
		||||
        <enum>QFrame::Shadow::Sunken</enum>
 | 
			
		||||
       </property>
 | 
			
		||||
       <property name="midLineWidth">
 | 
			
		||||
        <number>0</number>
 | 
			
		||||
       </property>
 | 
			
		||||
       <property name="orientation">
 | 
			
		||||
        <enum>Qt::Orientation::Horizontal</enum>
 | 
			
		||||
       </property>
 | 
			
		||||
      </widget>
 | 
			
		||||
     </item>
 | 
			
		||||
     <item>
 | 
			
		||||
      <widget class="QLabel" name="label_4">
 | 
			
		||||
       <property name="frameShadow">
 | 
			
		||||
        <enum>QFrame::Shadow::Sunken</enum>
 | 
			
		||||
       </property>
 | 
			
		||||
       <property name="text">
 | 
			
		||||
        <string>memo</string>
 | 
			
		||||
       </property>
 | 
			
		||||
       <property name="alignment">
 | 
			
		||||
        <set>Qt::AlignmentFlag::AlignCenter</set>
 | 
			
		||||
       </property>
 | 
			
		||||
      </widget>
 | 
			
		||||
     </item>
 | 
			
		||||
     <item>
 | 
			
		||||
      <layout class="QHBoxLayout" name="horizontalLayout" stretch="1,9">
 | 
			
		||||
       <item>
 | 
			
		||||
        <widget class="QLabel" name="label_6">
 | 
			
		||||
         <property name="text">
 | 
			
		||||
          <string>TextLabel</string>
 | 
			
		||||
         </property>
 | 
			
		||||
         <property name="alignment">
 | 
			
		||||
          <set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
 | 
			
		||||
         </property>
 | 
			
		||||
        </widget>
 | 
			
		||||
       </item>
 | 
			
		||||
       <item>
 | 
			
		||||
        <widget class="QLineEdit" name="lineEdit">
 | 
			
		||||
         <property name="maximumSize">
 | 
			
		||||
          <size>
 | 
			
		||||
           <width>200</width>
 | 
			
		||||
           <height>16777215</height>
 | 
			
		||||
          </size>
 | 
			
		||||
         </property>
 | 
			
		||||
        </widget>
 | 
			
		||||
       </item>
 | 
			
		||||
      </layout>
 | 
			
		||||
     </item>
 | 
			
		||||
     <item>
 | 
			
		||||
      <widget class="QLabel" name="label_5">
 | 
			
		||||
       <property name="text">
 | 
			
		||||
        <string>TextLabel</string>
 | 
			
		||||
       </property>
 | 
			
		||||
      </widget>
 | 
			
		||||
     </item>
 | 
			
		||||
    </layout>
 | 
			
		||||
   </item>
 | 
			
		||||
  </layout>
 | 
			
		||||
 </widget>
 | 
			
		||||
 <resources/>
 | 
			
		||||
 <connections/>
 | 
			
		||||
</ui>
 | 
			
		||||
							
								
								
									
										216
									
								
								toolbox/codes/ui/widget_bg_ui.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										216
									
								
								toolbox/codes/ui/widget_bg_ui.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,216 @@
 | 
			
		||||
import sys
 | 
			
		||||
from PySide6.QtWidgets import QWidget, QApplication, QSizePolicy, QVBoxLayout, QLabel, QFrame, QHBoxLayout, QLineEdit, QMessageBox
 | 
			
		||||
from PySide6.QtGui import QPixmap, QPainter, QFontDatabase, QFont, QBrush, QShortcut, QKeySequence, QColor
 | 
			
		||||
from PySide6.QtCore import Qt, QPoint, QDateTime, Signal, QTimer
 | 
			
		||||
from zhdate import ZhDate
 | 
			
		||||
from codes.common import clibs
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LunarClockLabel(QLabel):
 | 
			
		||||
    def __init__(self, parent=None):
 | 
			
		||||
        super().__init__(parent)
 | 
			
		||||
        self.setMinimumWidth(350)
 | 
			
		||||
        timer = QTimer(self)
 | 
			
		||||
        timer.timeout.connect(self.update_time)
 | 
			
		||||
        timer.start(1000)
 | 
			
		||||
        self.update_time()
 | 
			
		||||
 | 
			
		||||
    def update_time(self):
 | 
			
		||||
        dt = QDateTime.currentDateTime()
 | 
			
		||||
        g = dt.date()
 | 
			
		||||
        z = ZhDate.today()
 | 
			
		||||
        week = "一二三四五六日"[g.dayOfWeek() - 1]
 | 
			
		||||
        text = f"{g.year()}年{g.month()}月{g.day()}日 {z.chinese()[5:]} 星期{week} {dt.toString('hh:mm:ss')}"
 | 
			
		||||
        self.setText(text)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DoubleClickLabel(QLabel):
 | 
			
		||||
    doubleClicked = Signal()
 | 
			
		||||
 | 
			
		||||
    def mouseDoubleClickEvent(self, event):
 | 
			
		||||
        super().mouseDoubleClickEvent(event)
 | 
			
		||||
        self.doubleClicked.emit()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class WidgetWithBg(QWidget):
 | 
			
		||||
    on_closed = Signal()
 | 
			
		||||
    on_full_screen = Signal(int)
 | 
			
		||||
 | 
			
		||||
    def __init__(self, parent=None):
 | 
			
		||||
        super().__init__(parent)
 | 
			
		||||
        self.predos()
 | 
			
		||||
        self.init_ui()
 | 
			
		||||
        self.setup_slot()
 | 
			
		||||
 | 
			
		||||
    def predos(self):
 | 
			
		||||
        font_id = QFontDatabase.addApplicationFont(f"{clibs.base_path}/assets/media/font/OldEnglishTextMT/OldEnglishTextMT.ttf")
 | 
			
		||||
        family = QFontDatabase.applicationFontFamilies(font_id)[0]
 | 
			
		||||
        self.lb_font = QFont(family, 28, QFont.Weight.Medium)
 | 
			
		||||
        self.background_pixmap = QPixmap(clibs.bg)
 | 
			
		||||
 | 
			
		||||
    def init_ui(self):
 | 
			
		||||
        layout_v = QVBoxLayout()
 | 
			
		||||
        # 最上层的空白区
 | 
			
		||||
        self.lb_empty_up = QLabel(self)
 | 
			
		||||
        layout_v.addWidget(self.lb_empty_up)
 | 
			
		||||
        # 头像区
 | 
			
		||||
        self.lb_avatar = DoubleClickLabel(self)
 | 
			
		||||
        self.lb_avatar.setAlignment(Qt.AlignmentFlag.AlignCenter)
 | 
			
		||||
        avatar = QPixmap(clibs.avatar)
 | 
			
		||||
        avatar = self.circle_pixmap(avatar, 200)
 | 
			
		||||
        self.lb_avatar.setPixmap(avatar)
 | 
			
		||||
        self.lb_avatar.setScaledContents(True)
 | 
			
		||||
        self.lb_avatar.setFixedSize(144, 144)
 | 
			
		||||
        layout_v.addWidget(self.lb_avatar, alignment=Qt.AlignmentFlag.AlignCenter)
 | 
			
		||||
        # 艺术字区
 | 
			
		||||
        self.lb_name = QLabel(self)
 | 
			
		||||
        self.lb_name.setAlignment(Qt.AlignmentFlag.AlignCenter)
 | 
			
		||||
        self.lb_name.setText("Manford Fan · Code Create Life")
 | 
			
		||||
        self.lb_name.setStyleSheet("color: rgba(255,255,255,255);")
 | 
			
		||||
        self.lb_name.setFont(self.lb_font)
 | 
			
		||||
        layout_v.addWidget(self.lb_name)
 | 
			
		||||
        # 时间区-左横线
 | 
			
		||||
        layout_h = QHBoxLayout()
 | 
			
		||||
        self.line_left = QFrame(self)
 | 
			
		||||
        self.line_left.setFrameShape(QFrame.Shape.HLine)
 | 
			
		||||
        self.line_left.setFrameShadow(QFrame.Shadow.Plain)
 | 
			
		||||
        self.line_left.setStyleSheet("""
 | 
			
		||||
            QFrame {
 | 
			
		||||
                border: none;
 | 
			
		||||
                background-color: rgba(255, 255, 255, 40);
 | 
			
		||||
            }
 | 
			
		||||
        """)
 | 
			
		||||
        policy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed)
 | 
			
		||||
        self.line_left.setSizePolicy(policy)
 | 
			
		||||
        self.line_left.setLineWidth(1)
 | 
			
		||||
        self.line_left.setFixedWidth(100)
 | 
			
		||||
        # 时间区-右横线
 | 
			
		||||
        self.line_right = QFrame(self)
 | 
			
		||||
        self.line_right.setFrameShape(QFrame.Shape.HLine)
 | 
			
		||||
        self.line_right.setFrameShadow(QFrame.Shadow.Plain)
 | 
			
		||||
        self.line_right.setStyleSheet("""
 | 
			
		||||
            QFrame {
 | 
			
		||||
                border: none;
 | 
			
		||||
                background-color: rgba(255, 255, 255, 40);
 | 
			
		||||
            }
 | 
			
		||||
        """)
 | 
			
		||||
        policy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed)
 | 
			
		||||
        self.line_right.setSizePolicy(policy)
 | 
			
		||||
        self.line_right.setLineWidth(1)
 | 
			
		||||
        self.line_right.setFixedWidth(100)
 | 
			
		||||
        # 时间区-时间
 | 
			
		||||
        self.lb_time = LunarClockLabel(self)
 | 
			
		||||
        self.lb_time.setAlignment(Qt.AlignmentFlag.AlignCenter)
 | 
			
		||||
        self.lb_time.setStyleSheet("color: rgba(255,255,255,255);")
 | 
			
		||||
        self.lb_time.setFont(QFont("Consolas", 12, QFont.Weight.Bold))
 | 
			
		||||
        layout_h.addStretch(1)
 | 
			
		||||
        layout_h.addWidget(self.line_left)
 | 
			
		||||
        layout_h.addWidget(self.lb_time)
 | 
			
		||||
        layout_h.addWidget(self.line_right)
 | 
			
		||||
        layout_h.addStretch(1)
 | 
			
		||||
        layout_v.addLayout(layout_h)
 | 
			
		||||
        # layout_v.addWidget(self.line, alignment=Qt.AlignmentFlag.AlignCenter)
 | 
			
		||||
 | 
			
		||||
        self.lb_proverb = QLabel(self)
 | 
			
		||||
        self.lb_proverb.setAlignment(Qt.AlignmentFlag.AlignCenter)
 | 
			
		||||
        self.lb_proverb.setText(clibs.proverb)
 | 
			
		||||
        self.lb_proverb.setStyleSheet("color: rgba(255,255,255,255);")
 | 
			
		||||
        self.lb_proverb.setFont(QFont("Consolas", 14, QFont.Weight.Bold))
 | 
			
		||||
        layout_v.addWidget(self.lb_proverb)
 | 
			
		||||
 | 
			
		||||
        self.le_password = QLineEdit(self)
 | 
			
		||||
        self.le_password.setEchoMode(QLineEdit.EchoMode.Password)
 | 
			
		||||
        self.le_password.setFont(QFont("Consolas", 12, QFont.Weight.Normal))
 | 
			
		||||
        self.le_password.setMinimumWidth(300)
 | 
			
		||||
        self.le_password.setFixedHeight(30)
 | 
			
		||||
        self.hide_le_password()
 | 
			
		||||
        layout_v.addWidget(self.le_password, alignment=Qt.AlignmentFlag.AlignCenter)
 | 
			
		||||
 | 
			
		||||
        self.lb_empty_down = QLabel(self)
 | 
			
		||||
        layout_v.addWidget(self.lb_empty_down)
 | 
			
		||||
 | 
			
		||||
        layout_v.setStretch(0, 2)  # empty up
 | 
			
		||||
        layout_v.setStretch(1, 2)  # avatar
 | 
			
		||||
        layout_v.setStretch(2, 1)  # name
 | 
			
		||||
        layout_v.setStretch(3, 2)  # time
 | 
			
		||||
        layout_v.setStretch(4, 1)  # proverb
 | 
			
		||||
        layout_v.setStretch(5, 1)  # password
 | 
			
		||||
        layout_v.setStretch(6, 2)  # empty down
 | 
			
		||||
        self.setLayout(layout_v)
 | 
			
		||||
 | 
			
		||||
    def setup_slot(self):
 | 
			
		||||
        self.lb_avatar.doubleClicked.connect(self.auth_show)
 | 
			
		||||
        self.le_password.returnPressed.connect(self.validate_password)
 | 
			
		||||
        QShortcut(QKeySequence("Ctrl+Alt+L"), self, self.auth_show)
 | 
			
		||||
        QShortcut(QKeySequence("Ctrl+Alt+S"), self, lambda: self.on_full_screen.emit(0))
 | 
			
		||||
        # QShortcut(QKeySequence("Esc"), self).activated.connect(lambda: self.on_full_screen.emit(1))
 | 
			
		||||
 | 
			
		||||
    def auth_show(self):
 | 
			
		||||
        if self.input_hide:
 | 
			
		||||
            self.show_le_password()
 | 
			
		||||
        else:
 | 
			
		||||
            self.hide_le_password()
 | 
			
		||||
 | 
			
		||||
    def show_le_password(self):
 | 
			
		||||
        self.input_hide = False
 | 
			
		||||
        self.le_password.clear()
 | 
			
		||||
        self.le_password.setPlaceholderText("Password")
 | 
			
		||||
        self.le_password.setStyleSheet("")
 | 
			
		||||
        self.le_password.setDisabled(False)
 | 
			
		||||
        self.le_password.setFocus()
 | 
			
		||||
 | 
			
		||||
    def hide_le_password(self):
 | 
			
		||||
        self.input_hide = True
 | 
			
		||||
        self.le_password.clear()
 | 
			
		||||
        self.le_password.setDisabled(True)
 | 
			
		||||
        self.le_password.setPlaceholderText("")
 | 
			
		||||
        self.le_password.setStyleSheet("background:transparent; color:rgba(0,0,0,0); border:none; ")
 | 
			
		||||
 | 
			
		||||
    def validate_password(self):
 | 
			
		||||
        password = self.le_password.text()
 | 
			
		||||
        if password == clibs.password:
 | 
			
		||||
            self.on_closed.emit()
 | 
			
		||||
            self.close()
 | 
			
		||||
        elif password == "":
 | 
			
		||||
            self.hide_le_password()
 | 
			
		||||
            return
 | 
			
		||||
        else:
 | 
			
		||||
            QMessageBox.critical(self, "错误", "密码不正确,请确认后重新输入!")
 | 
			
		||||
            self.show_le_password()
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def circle_pixmap(src: QPixmap, diameter: int) -> QPixmap:
 | 
			
		||||
        dst = QPixmap(diameter, diameter)
 | 
			
		||||
        dst.fill(Qt.GlobalColor.transparent)
 | 
			
		||||
        painter = QPainter(dst)
 | 
			
		||||
        painter.setRenderHint(QPainter.RenderHint.Antialiasing)
 | 
			
		||||
        painter.setPen(Qt.PenStyle.NoPen)
 | 
			
		||||
        painter.setBrush(QBrush(src))
 | 
			
		||||
        painter.drawEllipse(dst.rect())
 | 
			
		||||
        painter.end()
 | 
			
		||||
        return dst
 | 
			
		||||
 | 
			
		||||
    def paintEvent(self, event):
 | 
			
		||||
        if not self.background_pixmap.isNull():
 | 
			
		||||
            painter = QPainter(self)
 | 
			
		||||
            painter.setRenderHint(QPainter.RenderHint.Antialiasing)
 | 
			
		||||
 | 
			
		||||
            scaled_pixmap = self.background_pixmap.scaled(self.size(), Qt.AspectRatioMode.IgnoreAspectRatio, Qt.TransformationMode.SmoothTransformation)
 | 
			
		||||
            x = (self.width() - scaled_pixmap.width()) // 2
 | 
			
		||||
            y = (self.height() - scaled_pixmap.height()) // 2
 | 
			
		||||
            painter.drawPixmap(QPoint(x, y), scaled_pixmap)
 | 
			
		||||
 | 
			
		||||
            painter.setBrush(QColor(0, 0, 0, 144))  # 144 ≈ 50% 暗度,越大越暗
 | 
			
		||||
            painter.setPen(Qt.PenStyle.NoPen)
 | 
			
		||||
            painter.drawRect(self.rect())
 | 
			
		||||
            painter.end()
 | 
			
		||||
 | 
			
		||||
        super().paintEvent(event)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    app = QApplication(sys.argv)
 | 
			
		||||
    your_widget = WidgetWithBg()
 | 
			
		||||
    your_widget.resize(1000, 450)
 | 
			
		||||
    your_widget.show()
 | 
			
		||||
    sys.exit(app.exec())
 | 
			
		||||
		Reference in New Issue
	
	Block a user