Compare commits

...

5 Commits

Author SHA1 Message Date
062b1e24e9 qss 2025-10-10 17:16:08 +08:00
ed947743fc 修复一些问题 2025-09-29 11:26:29 +08:00
943130b875 完成日志界面的制作 2025-09-28 18:21:48 +08:00
1c47497fc2 总线通信架构整改完成 2025-09-27 19:03:05 +08:00
c7c0d15545 提交一下,准备更换总线通信架构 2025-09-27 08:36:08 +08:00
82 changed files with 1479 additions and 422 deletions

3
.gitignore vendored
View File

@@ -3,4 +3,5 @@
rokae/testbench.py
**/__pycache__/
NOTRACK/
gui/assets/database/
toolbox/assets/database/
toolbox/output/

View File

@@ -1,5 +1,5 @@
{
"ip_addr": "192.168.0.160",
"ip_addr": "192.168.2.160",
"ssh_port": "22",
"socket_port": 5050,
"xService_port": 6666,

View File

@@ -1,6 +1,7 @@
{
"minimum_password_length": 8,
"maximum_db_number": 10,
"log_number_per_page": 30,
"database_path": "",
"api_ali_pay": "",
"api_wechat_pay": ""

View File

@@ -0,0 +1,27 @@
LunarClockLabel#lb_time {
color: rgba(255,255,255,255);
font: 96px "Arial Black";
font-weight: bold;
}
LunarClockLabel#lb_date {
color: rgba(255,255,255,255);
font: 18px "Consolas";
font-weight: bold;
}
QLabel#lb_name {
color: rgba(255,255,255,255);
}
QFrame#line_left,
QFrame#line_right {
border: none;
background-color: rgba(255, 255, 255, 40);
}
QLabel#lb_proverb {
color: rgba(255,255,255,255);
font: 20px "Consolas";
font-weight: bold;
}

View File

@@ -0,0 +1,92 @@
/* ------ 整体画布 ------ */
QListWidget#SListWidget {
background: #ffffff;
border: 1px solid #d0d7de;
border-radius: 4px;
outline: none;
color: #24292f; /* 默认文字色 */
font-family: "Consolas";
font-size: 18px;
gridline-color: transparent; /* 网格线 */
/* 交替行颜色(需代码 setAlternatingRowColors(true) */
alternate-background-color: #f6f8fa;
/* 滚动条圆角背景 */
background-clip: padding;
}
/* ------ 单项(行) ------ */
QListWidget#SListWidget::item {
height: 36px; /* 固定行高 */
padding-left: 12px;
padding-right: 8px;
border: none;
/* 行内文字对齐方式 */
text-align: left;
}
/* 选中态 */
QListWidget#SListWidget::item:selected {
background: #0969da;
color: #ffffff;
border-left: 0px solid #0550ae; /* 左侧高亮条 */
}
/* 悬停态(未选中) */
QListWidget#SListWidget::item:hover:!selected {
background: rgba(9, 105, 218, 12%);
border-left: 4px solid transparent;
}
/* 禁用态 */
QListWidget#SListWidget::item:disabled {
color: #8c959f;
background: transparent;
}
/* ------ 图标区域 ------ */
QListWidget#SListWidget::icon {
padding-right: 8px; /* 图标与文字间距 */
}
/* ------ 滚动条(仅影响 #SListWidget ------ */
QListWidget#SListWidget QScrollBar:vertical {
width: 10px;
background: transparent;
border-radius: 5px;
}
QListWidget#SListWidget QScrollBar::handle:vertical {
background: #c0c6cc;
border-radius: 5px;
min-height: 30px;
}
QListWidget#SListWidget QScrollBar::handle:vertical:hover {
background: #a0a6ac;
}
QListWidget#SListWidget QScrollBar::add-line:vertical,
QListWidget#SListWidget QScrollBar::sub-line:vertical {
height: 0; /* 隐藏上下箭头 */
}
QListWidget#SListWidget QScrollBar:horizontal {
height: 10px;
background: transparent;
border-radius: 5px;
}
QListWidget#SListWidget QScrollBar::handle:horizontal {
background: #c0c6cc;
border-radius: 5px;
min-width: 30px;
}
QListWidget#SListWidget QScrollBar::handle:horizontal:hover {
background: #a0a6ac;
}
QListWidget#SListWidget QScrollBar::add-line:horizontal,
QListWidget#SListWidget QScrollBar::sub-line:horizontal {
width: 0; /* 隐藏左右箭头 */
}
/* ------ 拖拽插入指示器(可选) ------ */
QListWidget#SListWidget::drop-indicator {
background: #0969da;
width: 2px;
}

View File

@@ -0,0 +1,6 @@
QStatusBar#SStatusBar {
background: #8B8989; /* 背景色 */
color: #000000; /* 文字色 */
border: none;
font: 16px "Consolas";
}

View File

@@ -0,0 +1,49 @@
QToolBar#SToolBar {
background: #eef1f5; /* 背景色 */
border: none; /* 去掉默认边框 */
border-bottom: 2px solid #d0d7de;/* 底部分隔线 */
spacing: 2px; /* 图标/按钮间距 */
padding: 2px 4px; /* 内边距:上下/左右 */
min-height: 20px; /* 最小高度 */
}
/* 工具栏里所有 QToolButton图标按钮 */
QToolBar#SToolBar QToolButton {
background: transparent;
border: 1px solid transparent;
border-radius: 8px;
color: #24292f;
font: 18px "Consolas";
padding: 0px 0px;
margin: 0px 0px;
}
/* 悬停 */
QToolBar#SToolBar QToolButton:hover {
background: rgba(9, 105, 218, 12%);
border-color: #0969da;
}
/* 按下/选中 */
QToolBar#SToolBar QToolButton:pressed,
QToolBar#SToolBar QToolButton:checked {
background: #0969da;
color: #ffffff;
border-color: #0550ae;
}
/* 仅工具栏里 objectName = ac_switch 的按钮 */
QToolBar#SToolBar QToolButton#ac_switch {
background: #eef1f5;
border: none;
border-radius: 4px;
color: #fff;
font: 14px "Microsoft YaHei";
padding: 4px 6px;
}
QToolBar#SToolBar QToolButton#ac_switch:hover {
background: #eef1f5;
}
QToolBar#toolBar QToolButton#ac_switch:pressed {
background: #0550ae;
}

View File

@@ -0,0 +1,121 @@
/* ------ 整体画布 ------ */
QTreeWidget#treeW {
background: #ffffff;
border: 1px solid #d0d7de;
border-radius: 8px;
outline: none;
color: #24292f; /* 默认文字色 */
font-family: "Consolas";
font-size: 16px;
gridline-color: #e1e4e8; /* 网格线 */
show-decoration-selected: 1; /* 整行选中 */
background-clip: padding;
}
/* ------ 表头 ------ */
QHeaderView#header {
background: #eef1f5;
border-left: 1px solid #d0d7de;
border-bottom: 1px solid #d0d7de;
border-right: none; /* 最右列去线 */
font-weight: 600;
font-size: 16px;
color: #57606a;
}
QHeaderView#header:section {
padding: 6px 10px;
}
QHeaderView#header:section:hover {
background: rgba(9, 105, 218, 12%);
}
/* ------ 行item ------ */
QTreeWidget#treeW::item {
height: 25px; /* 固定行高 */
padding-left: 8px;
/*border-right: 1px solid #e1e4e8;
border-bottom: 1px solid #e1e4e8;*/
}
QTreeWidget#treeW::item:selected {
background: #0969da;
color: #ffffff;
border-left: 0px solid #0550ae; /* 左侧高亮条 */
}
QTreeWidget#treeW::item:hover:!selected {
background: rgba(9, 105, 218, 12%);
}
QTreeWidget#treeW::item:disabled {
color: #8c959f;
background: transparent;
}
/* ------ 分支指示器(三角) ------ */
QTreeWidget#treeW::branch {
width: 0px;
height: 0px;
image: none; /* 也不画三角 */
}
/* ------ 滚动条(仅影响 #treeW ------ */
QTreeWidget#treeW QScrollBar:vertical {
width: 10px;
background: transparent;
border-radius: 5px;
}
QTreeWidget#treeW QScrollBar::handle:vertical {
background: #c0c6cc;
border-radius: 3px;
min-height: 20px;
}
QTreeWidget#treeW QScrollBar::handle:vertical:hover {
background: #a0a6ac;
}
QTreeWidget#treeW QScrollBar::add-line:vertical,
QTreeWidget#treeW QScrollBar::sub-line:vertical {
height: 0; /* 隐藏箭头 */
}
QTreeWidget#treeW QScrollBar:horizontal {
height: 10px;
background: transparent;
border-radius: 5px;
}
QTreeWidget#treeW QScrollBar::handle:horizontal {
background: #c0c6cc;
border-radius: 5px;
min-width: 20px;
}
QTreeWidget#treeW QScrollBar::handle:horizontal:hover {
background: #a0a6ac;
}
QTreeWidget#treeW QScrollBar::add-line:horizontal,
QTreeWidget#treeW QScrollBar::sub-line:horizontal {
width: 0;
}
QPushButton#pb_search,
QPushButton#pb_previous,
QPushButton#pb_next {
font-family: "Consolas";
font-size: 18px;
}
QLabel#lb_page {
font-family: "Consolas";
font-size: 18px;
}
QCheckBox#box_info,
QCheckBox#box_warning,
QCheckBox#box_error,
QCheckBox#box_exception,
QCheckBox#box_unknown {
font-family: "Consolas";
font-size: 18px;
}
QLineEdit#le_search {
font-family: "Consolas";
font-size: 18px;
}

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 327 KiB

After

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 329 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 327 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 327 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 327 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 328 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 320 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 KiB

View File

Before

Width:  |  Height:  |  Size: 326 KiB

After

Width:  |  Height:  |  Size: 326 KiB

View File

Before

Width:  |  Height:  |  Size: 322 KiB

After

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB

View File

Before

Width:  |  Height:  |  Size: 324 KiB

After

Width:  |  Height:  |  Size: 324 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -1,15 +1,33 @@
from pathlib import Path
from threading import Lock
base_path = Path(__file__).resolve().parent.parent.parent
lock = Lock()
account = None
config = 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
conn, cursor = None, None
listW_items = {"实用工具": "w10_practical", "效率提升": "w20_efficiency", "财务分析": "w30_financial"}
caller_frame = None
base_path = Path(__file__).resolve().parent.parent.parent
db_file = base_path / "assets/database/toolbox.db"
bg = f"{base_path}/assets/media/bg.jpg"
avatar = f"{base_path}/assets/media/avatar.jpg"
icon = f"{base_path}/assets/media/icon.ico"
logo = f"{base_path}/assets/media/logo.png"
on_icon = f"{base_path}/assets/media/switch_on.png"
off_icon = f"{base_path}/assets/media/switch_off.png"
qss_home_overlay = f"{base_path}/assets/conf/qss/home_overlay.qss"
qss_list_widget = f"{base_path}/assets/conf/qss/list_widget.qss"
qss_toolbar = f"{base_path}/assets/conf/qss/toolbar.qss"
qss_statusbar = f"{base_path}/assets/conf/qss/statusbar.qss"
qss_w08_log = f"{base_path}/assets/conf/qss/w08_log.qss"
def delete_files_in_directory(directory):
path = Path(directory)
if path.exists() and path.is_dir():

View File

@@ -1,11 +1,16 @@
import sqlite3
import time
from inspect import currentframe
from functools import singledispatch, wraps
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)
def db_init():
if clibs.db_file.exists():
return
conn = sqlite3.connect(clibs.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")
@@ -35,14 +40,20 @@ def db_init(db_file):
)
"""
)
cursor.execute(f"INSERT INTO logs (level, module, content) VALUES (?, ?, ?)", ("info", "login_ui", "数据库初始化成功!"))
cursor.close()
conn.close()
def db_lock(func):
@wraps(func)
def wrapper(*args, **kwargs):
clibs.caller_frame = currentframe().f_back
try:
clibs.lock.acquire(True)
ret = func(*args, **kwargs)
except Exception as e:
print(f"db operation error: {e}")
ret = None
finally:
clibs.lock.release()
return ret
@@ -50,31 +61,87 @@ def db_lock(func):
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()
db_file_backup.write_bytes(clibs.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.config["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
# import traceback, inspect
# print("[Conn] 被调用", traceback.format_stack()[-2])
# print("[Conn] conn=", clibs.conn, "cursor=", clibs.cursor)
if clibs.conn is not None:
return
clibs.conn = sqlite3.connect(clibs.db_file, isolation_level=None, check_same_thread=False, cached_statements=2048, timeout=3.0)
clibs.cursor = clibs.conn.cursor()
clibs.cursor.execute("PRAGMA journal_mode=wal")
clibs.cursor.execute("PRAGMA wal_checkpoint=TRUNCATE")
clibs.cursor.execute("PRAGMA synchronous=normal")
clibs.cursor.execute("PRAGMA temp_store=memory")
clibs.cursor.execute("PRAGMA mmap_size=30000000000")
clibs.cursor.execute("PRAGMA cache_size=200000")
@db_lock
def db_close(conn, cursor):
cursor.close()
conn.close()
def db_close():
if clibs.cursor is not None:
clibs.cursor.close()
if clibs.conn is not None:
clibs.conn.close()
clibs.conn, clibs.cursor = None, None
@db_lock
def db_write_logs(content, module="", level="info"):
if module == "" and clibs.caller_frame is not None:
module_name = clibs.caller_frame.f_globals["__name__"].split(".")[-1] #
func_name = clibs.caller_frame.f_code.co_name
line_no = clibs.caller_frame.f_lineno
module = f"{module_name}-{func_name}:{line_no}"
if level.lower() not in ["info", "warning", "error", "exception"]:
level = "unknown"
clibs.cursor.execute(f"INSERT INTO logs (level, module, content) VALUES (?, ?, ?)", (level, module, content))
@singledispatch
@db_lock
def db_query_logs(dummy: object = None):
clibs.cursor.execute(f"SELECT * FROM logs")
records = clibs.cursor.fetchall()
len_records = len(records)
return records, len_records
@db_query_logs.register
def _(levels: list):
placeholders = ",".join("?" * len(levels))
clibs.cursor.execute(f"SELECT * FROM logs WHERE level IN ({placeholders})", (*levels, ))
records = clibs.cursor.fetchall()
len_records = len(records)
return records, len_records
@db_query_logs.register
def _(search_text: str, records: list):
ids = [_[0] for _ in records]
placeholder = ",".join("?" * len(ids))
clibs.cursor.execute(f"SELECT * FROM logs WHERE id IN ({placeholder}) and content like ?", (ids + [f"%{search_text}%", ]))
records = clibs.cursor.fetchall()
len_records = len(records)
return records, len_records
@db_lock
def db_write_users(username, password_encrypted, salt):
clibs.cursor.execute("INSERT INTO users (username, password, salt) VALUES (?, ?, ?)", (username, password_encrypted, salt))
@db_lock
def db_delete_users(username):
# clibs.cursor.execute("INSERT INTO users (username, password, salt) VALUES (?, ?, ?)", (username, password_encrypted, salt))
...
@db_lock
def db_query_users(username):
clibs.cursor.execute(f""" SELECT * FROM users where username = "{username}" """)
record = clibs.cursor.fetchall()
return record

View File

@@ -0,0 +1,29 @@
from functools import wraps
from codes.common import db_operation
from inspect import currentframe
import traceback
def handle_exception(stop: bool = False):
def exceptions(func):
@wraps(func)
def wrapper(*args, **kwargs):
real_frame = currentframe().f_back
try:
return func(*args, **kwargs)
except Exception as e:
for frame, lineno in traceback.walk_tb(e.__traceback__):
if frame.f_code.co_name == func.__name__:
real_frame = frame
break
tb = traceback.format_exc() # 完整堆栈
module_name = real_frame.f_globals["__name__"].split(".")[-1]
func_name = real_frame.f_code.co_name
line_no = real_frame.f_lineno
module = f"{module_name}-{func_name}:{line_no}"
db_operation.db_write_logs(tb, module, "exception")
if stop:
raise e
return wrapper
return exceptions

View File

@@ -0,0 +1,41 @@
from pathlib import Path
from PySide6.QtCore import QObject, QFileSystemWatcher
class QssReloader(QObject):
instance = None
def __new__(cls, *args, **kwargs):
if cls.instance is None:
cls.instance = super().__new__(cls)
return cls.instance
def __init__(self):
super().__init__()
self.path2objs = {}
self.watcher = QFileSystemWatcher(self)
self.watcher.fileChanged.connect(self.on_file_changed)
def register(self, qss_path: str, ui_obj):
path = str(Path(qss_path).resolve())
obj_list = self.path2objs.setdefault(path, [])
if ui_obj not in obj_list:
obj_list.append(ui_obj)
if path not in self.watcher.files():
self.watcher.addPath(path)
self.apply_one(path, ui_obj)
def on_file_changed(self, path: str):
for obj in self.path2objs.get(path, []):
self.apply_one(path, obj)
if path not in self.watcher.files():
self.watcher.addPath(path)
@staticmethod
def apply_one(path: str, obj):
qss = Path(path).read_text(encoding="utf-8")
obj.setStyleSheet(qss)
qss_reloader = QssReloader()

View File

@@ -0,0 +1,30 @@
from PySide6.QtCore import QObject, Signal
class SignalBus(QObject):
instance = None
def __new__(cls):
if cls.instance is None:
cls.instance = super(SignalBus, cls).__new__(cls)
return cls.instance
def __init__(self):
super().__init__()
current_stacked_page = Signal(str) # 获取当前页面的page_id
init_stacked_page = Signal(str) # 设置打开侧边栏后的初始页面
stacked_page_switch = Signal(str) # 切换stacked widget的页面
stacked_page_switch_setting = Signal() # 切换stacked widget的设置页面后的触发信号
stacked_page_switch_log = Signal() # 切换stacked widget的日志页面后的触发信号
stacked_page_switch_about = Signal() # 切换stacked widget的关于页面后的触发信号
qa_switch_change = Signal(bool) # 切换折叠侧边栏的状态
home_overlay_trigger = Signal() # 触发软件锁屏
home_overlay_auth = Signal() # 触发密码框的显示与隐藏
home_overlay_close = Signal() # 退出锁屏后的收尾信号
list_widget_click = Signal(str) # 触发点击list widget信号
list_widget_on_off = Signal(bool) # 主动控制是否显示list widget组件
signal_bus = SignalBus()

View File

@@ -1,28 +0,0 @@
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

View File

View File

@@ -0,0 +1,58 @@
from PySide6.QtGui import QFocusEvent
from PySide6.QtWidgets import QListWidget, QListWidgetItem
from PySide6.QtCore import Qt
from codes.common import clibs
from codes.common.signal_bus import signal_bus
from codes.common.qss_reloader import qss_reloader
class SListWidget(QListWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.pre_do()
self.init_ui()
self.post_do()
def pre_do(self):
...
def init_ui(self):
self.setObjectName("SListWidget")
self.setMinimumWidth(150)
for item in clibs.listW_items:
_ = QListWidgetItem(item)
_.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.addItem(_)
def post_do(self):
qss_reloader.register(clibs.qss_list_widget, self)
self.setup_slot()
def setup_slot(self):
self.itemClicked.connect(self.item_clicked)
signal_bus.stacked_page_switch_setting.connect(self.qa_hide)
signal_bus.stacked_page_switch_log.connect(self.qa_hide)
signal_bus.stacked_page_switch_about.connect(self.qa_hide)
signal_bus.list_widget_on_off.connect(self.lw_show_hide)
def item_clicked(self, item):
page_id = clibs.listW_items[item.text()]
signal_bus.list_widget_click.emit(page_id)
def lw_show_hide(self, enabled: bool):
if enabled:
self.clearSelection()
self.show()
signal_bus.init_stacked_page.emit("w10_practical")
else:
self.hide()
def qa_hide(self):
self.hide()
signal_bus.qa_switch_change.emit(False)
def focusOutEvent(self, event: QFocusEvent):
self.clearSelection()
super().focusOutEvent(event)

View File

@@ -0,0 +1,41 @@
from PySide6.QtWidgets import QStackedWidget
from codes.common.signal_bus import signal_bus
from codes.ui.stacked_pages.w01_setting import W01Setting
from codes.ui.stacked_pages.w08_log import W08Log
from codes.ui.stacked_pages.w09_about import W09About
from codes.ui.stacked_pages.w10_practical import W10Practical
from codes.ui.stacked_pages.w20_efficiency import W20Efficiency
from codes.ui.stacked_pages.w30_financial import W30Financial
class SStackedWidget(QStackedWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.predos()
self.init_ui()
self.setup_slot()
def predos(self):
self.page_list = {"w01_setting": W01Setting(), "w08_log": W08Log(), "w09_about": W09About(), "w10_practical": W10Practical(), "w20_efficiency": W20Efficiency(), "w30_financial": W30Financial()}
def init_ui(self):
self.setObjectName("SStackedWidget")
# stacked widget 1x: 10为一级按钮页其余为二级按钮页2-9同理 | 0x. 日志/设置/关于等页面
for page_id, widget in self.page_list.items():
widget.setObjectName(page_id)
self.addWidget(widget)
w = self.page_list.get("w01_setting")
self.setCurrentWidget(w)
def setup_slot(self):
signal_bus.init_stacked_page.connect(self.set_current_page)
signal_bus.stacked_page_switch.connect(self.set_current_page)
signal_bus.list_widget_click.connect(self.set_current_page)
def set_current_page(self, page_id: str):
w = self.page_list.get(page_id)
self.setCurrentWidget(w)
signal_bus.current_stacked_page.emit(page_id)

View File

@@ -0,0 +1,25 @@
from PySide6.QtWidgets import QStatusBar
from PySide6.QtGui import QAction, QIcon
from PySide6.QtCore import QSize
from codes.common import clibs
from codes.common.signal_bus import signal_bus
from codes.common.qss_reloader import qss_reloader
class SStatusBar(QStatusBar):
def __init__(self, parent=None):
super().__init__(parent)
self.pre_do()
self.init_ui()
self.post_do()
def pre_do(self):
...
def init_ui(self):
self.setObjectName("SStatusBar")
...
def post_do(self):
qss_reloader.register(clibs.qss_statusbar, self)

View File

@@ -0,0 +1,102 @@
from PySide6.QtWidgets import QToolBar
from PySide6.QtGui import QAction, QIcon
from PySide6.QtCore import QSize
from codes.common import clibs
from codes.common.signal_bus import signal_bus
from codes.common.qss_reloader import qss_reloader
class SToolBar(QToolBar):
def __init__(self, parent=None):
super().__init__(parent)
self.pre_do()
self.init_ui()
self.post_do()
def pre_do(self):
...
def init_ui(self):
self.setObjectName("SToolBar")
# switch
self.on_icon = QIcon(clibs.on_icon) # 开状态图标
self.off_icon = QIcon(clibs.off_icon) # 关状态图标
self.ac_switch = QAction(self.on_icon, "", self)
self.ac_switch.setMenuRole(QAction.MenuRole.NoRole)
self.ac_switch.setStatusTip("Switch side bar")
self.ac_switch.setToolTip("Ctrl+Alt+T")
self.ac_switch.setCheckable(True)
self.ac_switch.setChecked(True)
self.setIconSize(QSize(30, 30))
self.addAction(self.ac_switch)
btn = self.widgetForAction(self.ac_switch) # 取出实际 QToolButton
btn.setObjectName("ac_switch")
# homepage
self.ac_homepage = QAction()
self.ac_homepage.setMenuRole(QAction.MenuRole.NoRole)
self.ac_homepage.setStatusTip("Go to home page")
self.ac_homepage.setToolTip("Ctrl+Alt+H")
self.ac_homepage.setText("主页")
self.addAction(self.ac_homepage)
# setting
self.ac_setting = QAction()
self.ac_setting.setMenuRole(QAction.MenuRole.NoRole)
self.ac_setting.setStatusTip("Go to setting page")
self.ac_setting.setToolTip("Ctrl+Alt+S")
self.ac_setting.setText("设置")
self.addAction(self.ac_setting)
# log
self.ac_log = QAction()
self.ac_log.setMenuRole(QAction.MenuRole.NoRole)
self.ac_log.setStatusTip("Go to log page")
self.ac_log.setToolTip("Ctrl+Alt+L")
self.ac_log.setText("日志")
self.addAction(self.ac_log)
# about
self.ac_about = QAction()
self.ac_about.setMenuRole(QAction.MenuRole.NoRole)
self.ac_about.setStatusTip("Go to about page")
self.ac_about.setToolTip("Ctrl+Alt+A")
self.ac_about.setText("关于")
self.addAction(self.ac_about)
def post_do(self):
qss_reloader.register(clibs.qss_toolbar, self)
self.setup_slot()
def setup_slot(self):
self.ac_switch.toggled.connect(self.ac_sw)
self.ac_homepage.triggered.connect(self.ac_hp)
self.ac_setting.triggered.connect(self.ac_sp)
self.ac_log.triggered.connect(self.ac_lp)
self.ac_about.triggered.connect(self.ac_ap)
signal_bus.qa_switch_change.connect(self.change2hide)
def ac_sw(self, checked: bool):
self.ac_switch.setIcon(self.on_icon if checked else self.off_icon)
signal_bus.list_widget_on_off.emit(checked)
def ac_hp(self):
signal_bus.home_overlay_trigger.emit()
def ac_sp(self):
signal_bus.stacked_page_switch.emit("w01_setting")
signal_bus.stacked_page_switch_setting.emit()
def ac_lp(self):
signal_bus.stacked_page_switch.emit("w08_log")
signal_bus.stacked_page_switch_log.emit()
def ac_ap(self):
signal_bus.stacked_page_switch.emit("w09_about")
signal_bus.stacked_page_switch_about.emit()
def change2hide(self):
self.ac_switch.setChecked(False)
self.ac_switch.setIcon(self.off_icon)

View File

@@ -1,64 +1,80 @@
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 PySide6.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QTabWidget, QLineEdit, QMessageBox, QSizePolicy
from PySide6.QtCore import Qt, QSize
from PySide6.QtGui import QFont, QIcon, QPixmap, QShortcut
from codes.common import clibs, db_operation
from codes.common.secure_encrypt import PassCipher
from codes.ui import main_ui
from codes.common.exception_handler import handle_exception
class LoginWindow(QWidget):
def __init__(self):
super().__init__()
self.pre_do()
self.init_ui()
self.setup_slot()
self.predos()
self.le_username.setFocus()
self.post_do()
def pre_do(self):
db_operation.db_init()
db_operation.db_conn()
def init_ui(self):
self.setObjectName("LoginWindow")
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.setWindowIcon(QIcon(clibs.icon))
self.setFont(QFont("Consolas", 14))
self.layout_outter = QHBoxLayout()
self.layout_outer = QHBoxLayout()
self.lb_logo = QLabel()
self.lb_logo.setObjectName("lb_logo")
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.setPixmap(QPixmap(clibs.logo))
self.lb_logo.setFixedSize(QSize(120, 120))
self.lb_logo.setScaledContents(True)
self.layout_outter.addWidget(self.lb_logo)
self.layout_outer.addWidget(self.lb_logo)
self.tabW_login = QTabWidget()
self.tabW_login.setObjectName("tabW_login")
self.tab_login = QWidget()
self.tab_login.setObjectName("tab_login")
self.tabW_login.addTab(self.tab_login, "登录")
self.tab_register = QWidget()
self.tab_register.setObjectName("tab_register")
self.tabW_login.addTab(self.tab_register, "注册")
self.layout_outter.addWidget(self.tabW_login)
self.layout_outer.addWidget(self.tabW_login)
# 登陆页面
self.layout_H_username = QHBoxLayout()
self.lb_username = QLabel("账号")
self.lb_username.setObjectName("lb_username")
self.lb_username.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.layout_H_username.addWidget(self.lb_username)
self.le_username = QLineEdit()
self.le_username.setObjectName("le_username")
self.le_username.setFocus()
self.layout_H_username.addWidget(self.le_username)
self.layout_H_password = QHBoxLayout()
self.lb_password = QLabel("密码")
self.lb_password.setObjectName("lb_password")
self.lb_password.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.layout_H_password.addWidget(self.lb_password)
self.le_password = QLineEdit()
self.le_password.setObjectName("le_password")
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.setObjectName("btn_login")
self.btn_login.setAutoDefault(True)
self.btn_cancel = QPushButton("取消")
self.btn_cancel.setObjectName("btn_cancel")
self.btn_cancel.setAutoDefault(True)
self.layout_H_button.addWidget(self.btn_login)
self.layout_H_button.addWidget(self.btn_cancel)
@@ -72,32 +88,40 @@ class LoginWindow(QWidget):
# 注册页面
self.layout_H_username_reg = QHBoxLayout()
self.lb_username_reg = QLabel("账号设定")
self.lb_username_reg.setObjectName("lb_username_reg")
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.setObjectName("le_username_reg")
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.setObjectName("lb_password_reg")
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.setObjectName("le_password_reg")
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.setObjectName("lb_password_reg_confirm")
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.setObjectName("le_password_reg")
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.setObjectName("btn_login_reg")
self.btn_login_reg.setAutoDefault(True)
self.btn_cancel_reg = QPushButton("取消")
self.btn_cancel_reg.setObjectName("btn_cancel_reg")
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)
@@ -109,7 +133,11 @@ class LoginWindow(QWidget):
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)
self.setLayout(self.layout_outer)
self.le_username.setFocus()
def post_do(self):
self.setup_slot()
def setup_slot(self):
self.tabW_login.currentChanged.connect(self.onChange_tabW)
@@ -117,14 +145,11 @@ class LoginWindow(QWidget):
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)
QShortcut("Esc", self).activated.connect(self.close)
QShortcut("Alt+1", self).activated.connect(lambda: self.tabW_login.setCurrentIndex(0))
QShortcut("Alt+2", self).activated.connect(lambda: self.tabW_login.setCurrentIndex(1))
def onChange_tabW(self):
text = self.tabW_login.tabText(self.tabW_login.currentIndex())
@@ -137,98 +162,93 @@ class LoginWindow(QWidget):
self.le_password_reg.clear()
self.le_password_reg_confirm.clear()
self.le_username_reg.setFocus()
else:
raise Exception(f"Unknown TabWidget Name: {text}")
@handle_exception()
def login_check(self):
def login_failed():
def login_failed(flag: int = 0):
reason = {-1: "用户名或密码为空", 0: "用户名未注册", 1: "解密成功,密码错误", 2: "解密失败,密码错误", 3: "数据库中有重复的用户名"}
self.le_username.clear()
self.le_password.clear()
self.le_username.setFocus()
QMessageBox.critical(self, "错误", "账号或密码错误,请重新输入!")
raise Exception(f"username:{username} login failed, reason: {reason[flag]}")
def validate_login():
nonlocal username, password
conn, cursor = db_operation.db_conn()
cursor.execute(f""" SELECT * FROM users where username = "{username}" """)
record = cursor.fetchall()
record = db_operation.db_query_users(username)
if username == "" or password == "":
login_failed(flag=-1)
if len(record) == 0:
login_failed()
login_failed(flag=0)
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
login_failed(flag=1)
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
db_operation.db_write_logs(f"username:{username} 登录成功!")
self.deleteLater()
except ValueError:
login_failed()
return False
login_failed(flag=2)
else:
raise Exception(f"username duplicated: {username}")
login_failed(flag=3)
username = self.le_username.text()
password = self.le_password.text()
validate_login()
@handle_exception()
def register_check(self):
def register_failed(flag: int = 0):
reason = {-1: "用户名或密码为空", 0: "两次密码不一致", 1: "密码长度不合规", 2: "用户名已存在", 3: "数据库中有重复的用户名"}
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, "错误", "密码长度不符合要求,请重新输入!")
QMessageBox.critical(self, "错误", "账号错误,密码长度(>8)不合规,或两次输入密码不一致,请确认后重新输入!")
raise Exception(f"register failed, reason: {reason[flag]}")
def validate_register():
nonlocal username, password, password_confirm, record, conn, cursor
if password != password_confirm:
register_failed()
return False
nonlocal username, password, password_confirm
record = db_operation.db_query_users(username)
if username == "" or password == "" or password_confirm == "":
register_failed(flag=-1)
if len(password) < clibs.account["minimum_password_length"]:
if password != password_confirm:
register_failed(flag=0)
if len(password) < clibs.config["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))
db_operation.db_write_users(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
register_failed(flag=2)
else:
raise Exception(f"username duplicated: {username}")
register_failed(flag=3)
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()
def closeEvent(self, event):
event.accept()
if __name__ == '__main__':
app = QApplication(sys.argv)

View File

@@ -1,64 +1,116 @@
import json
from math import lgamma
from shutil import copy
from random import choice
from pathlib import Path
import sys
from random import choice
from shutil import copy
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
import json
import sys
from PySide6.QtWidgets import QApplication, QWidget, QHBoxLayout, QMessageBox, QMainWindow, QStatusBar
from PySide6.QtGui import QIcon, QResizeEvent, QShortcut, QKeySequence, QAction
from PySide6.QtCore import Qt, QFileSystemWatcher
from codes.common import clibs, db_operation
from codes.ui.widget_bg_ui import WidgetWithBg
from codes.ui.components.toolbar_ui import SToolBar
from codes.ui.components.list_widget_ui import SListWidget
from codes.ui.components.stacked_widget_ui import SStackedWidget
from codes.ui.components.statusbar_ui import SStatusBar
from codes.common.signal_bus import signal_bus
from codes.common.worker import Worker
from typing import Callable, Any
from codes.common.exception_handler import handle_exception
from codes.ui.overlay_page.overlay_ui import WidgetWithBg
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.pre_do()
self.init_ui()
self.setup_slot()
self.predos()
self.post_do()
def pre_do(self):
self.m = Path(__file__).stem
self.home_overlay = None
db_operation.db_backup()
db_operation.db_conn()
def init_ui(self):
self.setObjectName("MainWindow")
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.toolBar = SToolBar()
self.addToolBar(self.toolBar)
self.toolBar.setMovable(False)
self.centralW = QWidget()
self.setCentralWidget(self.centralW)
self.statusBar = QStatusBar()
self.statusBar = SStatusBar()
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)
layout_h = QHBoxLayout()
# list widget
self.listW = SListWidget()
layout_h.addWidget(self.listW, stretch=1)
self.stackedW =SStackedWidget()
layout_h.addWidget(self.stackedW, stretch=5)
self.centralW.setLayout(layout_h)
def setup_slot(self):
self.ac_homepage.triggered.connect(self.ac_hp)
# QShortcut("Esc", self).activated.connect(self.close)
signal_bus.home_overlay_trigger.connect(self.ac_hp)
signal_bus.home_overlay_close.connect(self.exit_overlay)
self.install_sc()
def predos(self):
self.home_overlay = None
db_operation.db_backup()
self.conn, self.cursor = db_operation.db_conn()
def post_do(self):
self.setup_slot()
self.setup_sc()
def setup_sc(self, enable: bool = True):
for sc_id, obj in self.sc_pool.items():
if isinstance(obj, QAction):
obj.setEnabled(enable)
elif isinstance(obj, QShortcut):
obj.setEnabled(enable)
if enable == False and (sc_id == "esc" or sc_id == "ctrl_alt_m") and self.home_overlay:
obj.setEnabled(True)
if (sc_id == "esc" or sc_id == "ctrl_alt_m") and self.home_overlay is None:
obj.setEnabled(False)
def install_sc(self):
self.sc_pool = {}
# QAction
self.toolBar.ac_switch.setShortcut(QKeySequence("Ctrl+Alt+T"))
self.sc_pool["ctrl_alt_t"] = self.toolBar.ac_switch
self.toolBar.ac_homepage.setShortcut(QKeySequence("Ctrl+Alt+H"))
self.sc_pool["ctrl_alt_h"] = self.toolBar.ac_homepage
self.toolBar.ac_setting.setShortcut(QKeySequence("Ctrl+Alt+S"))
self.sc_pool["ctrl_alt_s"] = self.toolBar.ac_setting
self.toolBar.ac_log.setShortcut(QKeySequence("Ctrl+Alt+L"))
self.sc_pool["ctrl_alt_l"] = self.toolBar.ac_log
self.toolBar.ac_about.setShortcut(QKeySequence("Ctrl+Alt+A"))
self.sc_pool["ctrl_alt_a"] = self.toolBar.ac_about
# QShortcut
self.sc_f11 = QShortcut(QKeySequence("F11"), self)
self.sc_f11.activated.connect(self.shortcut_f11)
self.sc_pool["f11"] = self.sc_f11
# Subpage
self.sc_Esc = QShortcut(QKeySequence("Esc"), self)
self.sc_Esc.activated.connect(signal_bus.home_overlay_auth.emit)
self.sc_pool["esc"] = self.sc_Esc
self.sc_caM = QShortcut(QKeySequence("Ctrl+Alt+M"), self)
self.sc_caM.activated.connect(self.toggle_full_screen)
self.sc_pool["ctrl_alt_m"] = self.sc_caM
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
if files:
rand_choice = choice(files)
else:
rand_choice = ""
return rand_choice, files
def del_repeat_proverb(proverbs: list):
_proverbs = []
@@ -67,6 +119,7 @@ class MainWindow(QMainWindow):
_proverbs.append(proverb)
return _proverbs
@handle_exception()
def get_resources():
# background image
bing = "https://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=8"
@@ -77,33 +130,46 @@ class MainWindow(QMainWindow):
for file in files:
image_names.append(file.name.removesuffix(".jpg"))
for image in res["images"]:
startdate = image["startdate"]
if startdate in image_names:
image_name = "-".join([image["startdate"], image["hsh"]])
if image_name 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
file = Path(f"{clibs.base_path}/assets/media/bg/{image_name}.jpg")
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)
# 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
proverb_file = Path(f"{clibs.base_path}/assets/media/hitokoto.json")
req = requests.get(hitokoto)
if not proverb_file.exists():
proverb_file.touch()
proverb_file.write_text("[]")
with open(proverb_file, "w") as f:
f.write("[]")
with open(proverb_file, mode="rt", encoding="utf-8") as f:
proverbs = json.load(f)
proverb = json.loads(req.text)
if None in proverb.values():
for k, v in proverb.items():
if v is None:
if v == "from_who":
proverb.update({k: "佚名"})
elif v == "from":
proverb.update({k: "不知道"})
else:
proverb.update({k: "-"})
proverbs.append(proverb)
proverbs = del_repeat_proverb(proverbs)
with open(proverb_file, mode="wt", encoding="utf-8") as f:
json.dump(proverbs, f, ensure_ascii=False)
@handle_exception()
def change_resources():
# avatar
src, _ = get_files(f"{clibs.base_path}/assets/media/avatar")
@@ -123,11 +189,9 @@ class MainWindow(QMainWindow):
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()
self.setup_sc(False)
width, height = self.width(), self.height()
if width > clibs.win_width:
self.resize(self.width()-1, self.height()-1)
@@ -138,44 +202,7 @@ class MainWindow(QMainWindow):
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):
def launch_get_resources(self, func, on_anything=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)
@@ -183,6 +210,28 @@ class MainWindow(QMainWindow):
self.td_get_resources.finished.connect(lambda: None)
self.td_get_resources.start()
def toggle_full_screen(self):
if self.isFullScreen():
self.setWindowFlags(self.windowFlags() ^ Qt.WindowType.WindowStaysOnTopHint)
self.show()
self.showMaximized()
else:
self.setWindowFlags(Qt.WindowType.WindowStaysOnTopHint)
self.showFullScreen()
def exit_overlay(self):
self.home_overlay = None
self.setup_sc()
if self.isFullScreen():
self.setWindowFlags(self.windowFlags() ^ Qt.WindowType.WindowStaysOnTopHint)
self.show()
self.showMaximized()
def shortcut_f11(self):
if not self.home_overlay:
self.ac_hp()
self.toggle_full_screen()
def resizeEvent(self, event: QResizeEvent):
super().resizeEvent(event)
if self.home_overlay:
@@ -195,7 +244,7 @@ class MainWindow(QMainWindow):
reply = QMessageBox.question(self, "退出", "\n程序可能在运行,确定要退出吗?")
if reply == QMessageBox.StandardButton.Yes:
db_operation.db_close(self.conn, self.cursor)
db_operation.db_close()
event.accept()
else:
event.ignore()

View File

@@ -1,26 +1,42 @@
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.QtGui import QPixmap, QPainter, QFontDatabase, QFont, QBrush, QColor
from PySide6.QtCore import Qt, QPoint, QDateTime, Signal, QTimer
from zhdate import ZhDate
from codes.common import clibs
from codes.common.signal_bus import signal_bus
from codes.common.qss_reloader import qss_reloader
class LunarClockLabel(QLabel):
def __init__(self, parent=None):
def __init__(self, parent=None, flag=0):
super().__init__(parent)
self.setMinimumWidth(350)
timer = QTimer(self)
timer.timeout.connect(self.update_time)
timer.start(1000)
self.update_time()
if flag == 0:
self.update_date()
timer.timeout.connect(self.update_date)
else:
self.update_time()
timer.timeout.connect(self.update_time)
def update_date(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}"
self.setText(text)
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')}"
text = f"{dt.toString('hh:mm')}"
self.setText(text)
@@ -33,100 +49,97 @@ class DoubleClickLabel(QLabel):
class WidgetWithBg(QWidget):
on_closed = Signal()
on_full_screen = Signal(int)
def __init__(self, parent=None):
super().__init__(parent)
self.predos()
self.pre_do()
self.init_ui()
self.setup_slot()
self.post_do()
def predos(self):
def pre_do(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.lb_font = QFont(family, 32, QFont.Weight.Medium)
self.background_pixmap = QPixmap(clibs.bg)
def init_ui(self):
self.setObjectName("WidgetWithBg")
layout_v = QVBoxLayout()
# 最上层的空白区
self.lb_empty_up = QLabel(self)
self.lb_empty_up.setObjectName("lb_empty_up")
layout_v.addWidget(self.lb_empty_up)
# 头像区
layout_h_1 = QHBoxLayout()
layout_h_1.addStretch(1)
self.lb_avatar = DoubleClickLabel(self)
self.lb_avatar.setObjectName("lb_avatar")
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)
layout_h_1.addWidget(self.lb_avatar)
self.lb_time = LunarClockLabel(self, flag=1)
self.lb_time.setObjectName("lb_time")
self.lb_time.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout_h_1.addWidget(self.lb_time)
layout_h_1.addStretch(1)
layout_v.addLayout(layout_h_1)
# 艺术字区
self.lb_name = QLabel(self)
self.lb_name.setObjectName("lb_name")
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.setObjectName("line_left")
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.setObjectName("line_right")
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))
self.lb_date = LunarClockLabel(self)
self.lb_date.setObjectName("lb_date")
self.lb_date.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout_h.addStretch(1)
layout_h.addWidget(self.line_left)
layout_h.addWidget(self.lb_time)
layout_h.addWidget(self.lb_date)
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.setObjectName("lb_proverb")
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.setObjectName("le_password")
self.le_password.setEchoMode(QLineEdit.EchoMode.Password)
self.le_password.setFont(QFont("Consolas", 12, QFont.Weight.Normal))
# 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.hide_le_password()
self.lb_empty_down = QLabel(self)
self.lb_empty_down.setObjectName("lb_empty_down")
layout_v.addWidget(self.lb_empty_down)
layout_v.setStretch(0, 2) # empty up
@@ -138,29 +151,31 @@ class WidgetWithBg(QWidget):
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 post_do(self):
qss_reloader.register(clibs.qss_home_overlay, self)
self.setup_slot()
def auth_show(self):
if self.input_hide:
self.show_le_password()
else:
def setup_slot(self):
self.lb_avatar.doubleClicked.connect(self.toggle_auth_show)
self.le_password.returnPressed.connect(self.validate_password)
signal_bus.home_overlay_auth.connect(self.toggle_auth_show)
def toggle_auth_show(self):
if self.le_is_visible:
self.hide_le_password()
else:
self.show_le_password()
def show_le_password(self):
self.input_hide = False
self.le_is_visible = True
self.le_password.clear()
self.le_password.setPlaceholderText("Password")
self.le_password.setStyleSheet("")
self.le_password.setStyleSheet("border:none; border-radius: 5px;")
self.le_password.setDisabled(False)
self.le_password.setFocus()
def hide_le_password(self):
self.input_hide = True
self.le_is_visible = False
self.le_password.clear()
self.le_password.setDisabled(True)
self.le_password.setPlaceholderText("")
@@ -169,14 +184,17 @@ class WidgetWithBg(QWidget):
def validate_password(self):
password = self.le_password.text()
if password == clibs.password:
self.on_closed.emit()
self.close()
self.hide_le_password()
signal_bus.home_overlay_close.emit()
self.deleteLater()
return True
elif password == "":
self.hide_le_password()
return
return False
else:
QMessageBox.critical(self, "错误", "密码不正确,请确认后重新输入!")
# QMessageBox.critical(self, "错误", "密码不正确,请确认后重新输入!")
self.show_le_password()
return False
@staticmethod
def circle_pixmap(src: QPixmap, diameter: int) -> QPixmap:

View File

@@ -0,0 +1,27 @@
from PySide6.QtWidgets import QWidget, QLabel
from codes.common import clibs
class W01Setting(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.predos()
self.ui_init()
self.setup_slot()
self.setup_sc()
def predos(self):
...
def ui_init(self):
self.lb_test = QLabel(f"testing text on widget: \n{__file__}", parent=self)
self.lb_test.setObjectName("lb_test")
def setup_slot(self):
...
def setup_sc(self):
...

View File

@@ -0,0 +1,286 @@
from PySide6.QtWidgets import QWidget, QLabel, QMessageBox, QVBoxLayout, QTreeWidget, QHeaderView, QHBoxLayout, QPushButton, QFrame, QLineEdit, QCheckBox, QTreeWidgetItem, QDialog, QPlainTextEdit, QApplication
from PySide6.QtCore import Qt, Signal, QSize
from PySide6.QtGui import QColor, QIcon, QKeySequence, QIntValidator, QShortcut
from codes.common.signal_bus import signal_bus
from codes.common import db_operation
from codes.common import clibs
from codes.common.qss_reloader import qss_reloader
class LogDialog(QDialog):
def __init__(self, content, parent=None):
super().__init__(parent)
self.setWindowTitle("日志详情")
self.setGeometry(100, 100, 700, 300)
self.center_on_screen()
layout = QVBoxLayout(self)
self.plain_text_edit = QPlainTextEdit()
self.plain_text_edit.setReadOnly(True)
self.plain_text_edit.setPlainText(content)
layout.addWidget(self.plain_text_edit)
def center_on_screen(self):
screen_geometry = QApplication.primaryScreen().geometry()
screen_width = screen_geometry.width()
screen_height = screen_geometry.height()
dialog_width = self.width()
dialog_height = self.height()
x = (screen_width - dialog_width) // 2
y = (screen_height - dialog_height) // 2
self.move(x, y)
class PageNumberInput(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.init_ui()
self.setup_slot()
def setup_slot(self):
QShortcut(QKeySequence("Esc"), self).activated.connect(self.reject)
self.le_page_number.returnPressed.connect(self.accept)
self.le_page_number.returnPressed.connect(self.get_page_number)
def init_ui(self):
self.setMinimumSize(300,100)
self.setMaximumSize(400,120)
self.resize(300, 100)
self.setWindowIcon(QIcon(clibs.icon))
self.setWindowTitle("输入页码")
layout_h = QHBoxLayout()
layout_h.addStretch(1)
self.le_page_number = QLineEdit(self)
self.le_page_number.setText("1")
self.le_page_number.selectAll()
self.le_page_number.setValidator(QIntValidator(0, 9999999, self))
layout_h.addWidget(self.le_page_number, stretch=4)
layout_h.addStretch(1)
self.setLayout(layout_h)
def get_page_number(self):
text = self.le_page_number.text()
return 1 if text == 0 else int(text)
class ClickableLabel(QLabel):
clicked = Signal()
def mousePressEvent(self, event):
if event.button() == Qt.MouseButton.LeftButton:
self.clicked.emit()
super().mousePressEvent(event)
class W08Log(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.pre_do()
self.ui_init()
self.post_do()
def pre_do(self):
self.records, self.len_records = "", ""
self.is_searching = False
self.max_item_number = clibs.config["log_number_per_page"]
def ui_init(self):
self.setObjectName("W08Log")
layout_v = QVBoxLayout(self)
self.treeW = QTreeWidget()
self.treeW.setObjectName("treeW")
self.treeW.setHeaderLabels(["ID", "时间戳", "告警级别", "模块信息", "告警内容"])
self.treeW.headerItem().setTextAlignment(0, Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
self.header = self.treeW.header()
self.header.setObjectName("header")
for i in range(self.treeW.columnCount()):
self.header.setSectionResizeMode(i, QHeaderView.ResizeMode.ResizeToContents)
layout_v.addWidget(self.treeW, stretch=9)
layout_h = QHBoxLayout()
self.pb_previous = QPushButton("上一页")
self.pb_previous.setObjectName("pb_previous")
layout_h.addWidget(self.pb_previous, stretch=1)
self.lb_page = ClickableLabel("999999/999999")
self.lb_page.setObjectName("lb_page")
self.lb_page.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.lb_page.setMinimumWidth(144)
layout_h.addWidget(self.lb_page, stretch=2)
self.pb_next = QPushButton("下一页")
self.pb_next.setObjectName("pb_next")
layout_h.addWidget(self.pb_next, stretch=1)
layout_h.addStretch(9)
self.frame_checkbox = QFrame()
self.frame_checkbox.setObjectName("frame_checkbox")
layout_h_checkbox = QHBoxLayout()
self.box_info = QCheckBox("通知", parent=self.frame_checkbox)
self.box_info.setObjectName("box_info")
self.box_info.setChecked(True)
layout_h_checkbox.addWidget(self.box_info, stretch=1)
self.box_warning = QCheckBox("告警", parent=self.frame_checkbox)
self.box_warning.setObjectName("box_warning")
self.box_warning.setChecked(True)
layout_h_checkbox.addWidget(self.box_warning, stretch=1)
self.box_error = QCheckBox("错误", parent=self.frame_checkbox)
self.box_error.setObjectName("box_error")
self.box_error.setChecked(True)
layout_h_checkbox.addWidget(self.box_error, stretch=1)
self.box_exception = QCheckBox("异常", parent=self.frame_checkbox)
self.box_exception.setObjectName("box_exception")
self.box_exception.setChecked(True)
layout_h_checkbox.addWidget(self.box_exception, stretch=1)
self.box_unknown = QCheckBox("未知", parent=self.frame_checkbox)
self.box_unknown.setObjectName("box_unknown")
self.box_unknown.setChecked(True)
layout_h_checkbox.addWidget(self.box_unknown, stretch=1)
layout_h.addLayout(layout_h_checkbox, stretch=4)
self.le_search = QLineEdit()
self.le_search.setObjectName("le_search")
self.le_search.setPlaceholderText("告警内容")
self.le_search.setMinimumWidth(300)
layout_h.addWidget(self.le_search, stretch=5)
self.pb_search = QPushButton("查找")
self.pb_search.setObjectName("pb_search")
layout_h.addWidget(self.pb_search, stretch=1)
layout_v.addLayout(layout_h, stretch=1)
self.setLayout(layout_v)
def post_do(self):
qss_reloader.register(clibs.qss_w08_log, self)
self.setup_slot()
self.setup_sc()
def setup_slot(self):
self.treeW.itemDoubleClicked.connect(self.show_single_log)
self.pb_previous.clicked.connect(self.previous_page)
self.pb_next.clicked.connect(self.next_page)
self.pb_search.clicked.connect(self.search_page)
self.le_search.returnPressed.connect(self.search_page)
self.lb_page.clicked.connect(self.goto_page)
signal_bus.stacked_page_switch_log.connect(self.show_latest_page)
def setup_sc(self):
...
def previous_page(self):
if not self.is_searching:
self.records, self.len_records = db_operation.db_query_logs(None)
current, total = self.lb_page.text().split("/")
page_number = int(current) - 1
self.show_page(self.records, self.len_records, page_number=page_number)
def next_page(self):
if not self.is_searching:
self.records, self.len_records = db_operation.db_query_logs(None)
current, total = self.lb_page.text().split("/")
page_number = int(current) + 1
self.show_page(self.records, self.len_records, page_number=page_number)
def search_page(self):
filters = {"info": self.box_info.isChecked(), "warning": self.box_warning.isChecked(), "error": self.box_error.isChecked(), "exception": self.box_exception.isChecked(), "unknown": self.box_unknown.isChecked()}
search_text = self.le_search.text().strip()
flag, levels = False, []
for level, enable in filters.items():
if not enable:
continue
flag = True
levels.append(level)
if not flag:
QMessageBox().warning(None, "警告", "至少选择一个过滤器!")
return
self.records, self.len_records = db_operation.db_query_logs(levels)
if search_text:
self.records, self.len_records = db_operation.db_query_logs(search_text, self.records)
self.is_searching = True
self.show_page(self.records, self.len_records, page_number=None)
def goto_page(self):
dlg = PageNumberInput()
if dlg.exec() != 1:
return
page_number = dlg.get_page_number()
if not self.is_searching:
self.records, self.len_records = db_operation.db_query_logs(None)
self.show_page(self.records, self.len_records, page_number=page_number)
def show_latest_page(self):
self.records, self.len_records = db_operation.db_query_logs(None)
self.is_searching = False
self.box_info.setChecked(True)
self.box_warning.setChecked(True)
self.box_error.setChecked(True)
self.box_exception.setChecked(True)
self.box_unknown.setChecked(True)
self.le_search.clear()
self.show_page(self.records, self.len_records, page_number=None)
def show_page(self, records, len_records, page_number: int | None):
if len_records == 0:
self.treeW.clear()
return
remainder = len_records % self.max_item_number
total = len_records // self.max_item_number + 1 if remainder else len_records // self.max_item_number
if page_number is None:
current = total
elif page_number <= 0:
current = 1
elif page_number < total:
current = page_number
else:
current = total
self.lb_page.setText(f"{current}/{total}")
if current == 1:
idx_start = 0
idx_end = self.max_item_number if len_records >= self.max_item_number else len_records
elif current == total:
remainder = len_records % self.max_item_number
idx_start = len_records - remainder if remainder else len_records - self.max_item_number
idx_end = len_records
else:
idx_start = self.max_item_number * (current-1)
idx_end = self.max_item_number * current
self.treeW.clear()
self.treeW.setUniformRowHeights(True)
for record in records[idx_start:idx_end]:
record = [str(_) for _ in record]
item = QTreeWidgetItem(self.treeW, record)
colors = {
"info": QColor(255, 255, 255), # 白色
"warning": QColor(244, 164, 96), # 棕橙色
"error": QColor(205, 92, 92), # 印度红
"exception": QColor(70, 130, 180), # 钢蓝色
"unknown": QColor(190, 190, 190) # 灰色
}
# colors = {"info": "#FFFFFF", "warning": "#F4A460", "error": "#CD5C5C", "exception": "#4682B4", "unknown": "#BEBEBE"}
level = record[2]
color = colors.get(level, QColor(255, 255, 255))
for col in range(self.treeW.columnCount()):
item.setBackground(col, color)
self.treeW.addTopLevelItem(item)
self.treeW.scrollToBottom()
def show_single_log(self, item, column):
log_id = f"id = {item.text(0)}"
log_ts = f"ts = {item.text(1)}"
log_level = f"level = {item.text(2)}"
log_module = f"module = {item.text(3)}\n"
deco_line = "=" * 40
log_msg = item.text(4)
content = "\n".join([log_id, log_ts, log_level, log_module, deco_line, log_msg])
dialog = LogDialog(content, self)
dialog.exec()

View File

@@ -0,0 +1,25 @@
from PySide6.QtWidgets import QWidget, QLabel
class W09About(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.predos()
self.ui_init()
self.setup_slot()
self.setup_sc()
def predos(self):
...
def ui_init(self):
self.lb_test = QLabel(f"testing text on widget: \n{__file__}", parent=self)
self.lb_test.setObjectName("lb_test")
def setup_slot(self):
...
def setup_sc(self):
...

View File

@@ -0,0 +1,25 @@
from PySide6.QtWidgets import QWidget, QLabel
class W10Practical(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.predos()
self.ui_init()
self.setup_slot()
self.setup_sc()
def predos(self):
...
def ui_init(self):
self.lb_test = QLabel(f"testing text on widget: \n{__file__}", parent=self)
self.lb_test.setObjectName("lb_test")
def setup_slot(self):
...
def setup_sc(self):
...

View File

@@ -0,0 +1,25 @@
from PySide6.QtWidgets import QWidget, QLabel
class W20Efficiency(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.predos()
self.ui_init()
self.setup_slot()
self.setup_sc()
def predos(self):
...
def ui_init(self):
self.lb_test = QLabel(f"testing text on widget: \n{__file__}", parent=self)
self.lb_test.setObjectName("lb_test")
def setup_slot(self):
...
def setup_sc(self):
...

View File

@@ -0,0 +1,25 @@
from PySide6.QtWidgets import QWidget, QLabel
class W30Financial(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.predos()
self.ui_init()
self.setup_slot()
self.setup_sc()
def predos(self):
...
def ui_init(self):
self.lb_test = QLabel(f"testing text on widget: \n{__file__}", parent=self)
self.lb_test.setObjectName("lb_test")
def setup_slot(self):
...
def setup_sc(self):
...

View File

@@ -1,145 +0,0 @@
<?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

@@ -1,26 +1,18 @@
import sys
import json
from PySide6.QtWidgets import QMainWindow, QApplication
from pathlib import Path
from codes.ui import login_ui, main_ui
from PySide6.QtWidgets import QApplication
from codes.ui.main_ui import MainWindow
from codes.ui.login_ui import LoginWindow
from codes.common import clibs
class LoginWindow(login_ui.LoginWindow):
def __init__(self):
super().__init__()
class MainWindow(main_ui.MainWindow):
def __init__(self):
super().__init__()
if __name__ == '__main__':
with open(f"{clibs.base_path}/assets/conf/config.json", mode="rt", encoding="utf-8") as f:
clibs.account = json.load(f)
clibs.config = eval(Path(f"{clibs.base_path}/assets/conf/config.json").read_text(encoding="utf-8"))
app = QApplication(sys.argv)
window = LoginWindow()
# window = MainWindow()
# window = LoginWindow()
window = MainWindow()
window.show()
sys.exit(app.exec())

View File

@@ -0,0 +1,30 @@
# 工具箱
## 功能
### 计算器
### 颜色板
### 时间转换
### ASCII码速查
### 账本
### 待办提醒
### 密码
## TODOs
### 自定义快捷键
### 修改密码
### 自定义图标
```commandline
pyinstaller --noconfirm --onedir --windowed --icon "D:\Syncthing\company\S-Program\Projects\toolbox\assets\media\icon.ico" --name "toolbox" --contents-directory "resource" --clean --log-level "WARN" --optimize "2" --add-data "D:\Syncthing\company\S-Program\Projects\toolbox\assets;assets/" "D:\Syncthing\company\S-Program\Projects\toolbox\main.py"
```