create my toolbox -- first commit

This commit is contained in:
2025-09-26 08:39:05 +08:00
parent 34c74bf3d0
commit bb3ae1e65a
273 changed files with 903 additions and 1052 deletions

View File

View File

View File

View 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)

View 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()

View 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

View 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()

View 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"])

View 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
View 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())

View 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>

View 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())