modify: h5 插件系统更新
This commit is contained in:
@@ -1,3 +1,8 @@
|
||||
import toml
|
||||
|
||||
config = toml.load("pyproject.toml")
|
||||
try:
|
||||
config = toml.load("pyproject.toml")
|
||||
except:
|
||||
config = {}
|
||||
|
||||
__all__ = ["config"]
|
||||
|
||||
@@ -9,3 +9,9 @@ def add_student(message_item: MessageItem):
|
||||
# time.sleep(3)
|
||||
print("点击了添加按钮")
|
||||
return {"message": "学生添加成功"}
|
||||
|
||||
|
||||
@TaskMounter.task(meta={"task_name": "test"})
|
||||
def test(message_item: MessageItem):
|
||||
# print(f"测试任务: {message_item}")
|
||||
return {"message": "测试成功"}
|
||||
|
||||
@@ -6,4 +6,4 @@ import json
|
||||
@ListenMounter.listener()
|
||||
def students(str_students_list: str):
|
||||
print("students signal received")
|
||||
listen_signals.listening_students_signal.emit(json.loads(str_students_list))
|
||||
listen_signals.listening_students_signal.emit(json.loads(str_students_list))
|
||||
|
||||
@@ -4,4 +4,4 @@ from .listen_signals import ListenSignals
|
||||
global_signals = GlobalSignals()
|
||||
listen_signals = ListenSignals()
|
||||
|
||||
__all__ = ["global_signals"]
|
||||
__all__ = ["global_signals"]
|
||||
|
||||
@@ -4,5 +4,7 @@ from PySide6.QtCore import Signal, QObject
|
||||
class GlobalSignals(QObject):
|
||||
|
||||
show_add_dialog_signal = Signal(dict)
|
||||
update_queue_state = Signal(dict, str)
|
||||
|
||||
__all__ = ["GlobalSignals"]
|
||||
|
||||
__all__ = ["GlobalSignals"]
|
||||
|
||||
@@ -5,4 +5,5 @@ class ListenSignals(QObject):
|
||||
|
||||
listening_students_signal = Signal(list)
|
||||
|
||||
__all__ = ["ListenSignals"]
|
||||
|
||||
__all__ = ["ListenSignals"]
|
||||
|
||||
@@ -15,7 +15,9 @@ QPushButton:pressed{
|
||||
}
|
||||
"""
|
||||
|
||||
ADD_BUTTON_STYLE = BUTTON_STYLE + """
|
||||
ADD_BUTTON_STYLE = (
|
||||
BUTTON_STYLE
|
||||
+ """
|
||||
QPushButton {
|
||||
background-color: #0d6efd;
|
||||
}
|
||||
@@ -26,8 +28,11 @@ QPushButton:pressed{
|
||||
background-color: #0a58ca;
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
DELETE_BUTTON_STYLE = BUTTON_STYLE + """
|
||||
DELETE_BUTTON_STYLE = (
|
||||
BUTTON_STYLE
|
||||
+ """
|
||||
QPushButton {
|
||||
background-color: #dc3545;
|
||||
}
|
||||
@@ -37,8 +42,11 @@ QPushButton:hover {
|
||||
QPushButton:pressed{
|
||||
background-color: #b02a37;
|
||||
"""
|
||||
)
|
||||
|
||||
BATCH_DELETE_BUTTON_STYLE = BUTTON_STYLE + """
|
||||
BATCH_DELETE_BUTTON_STYLE = (
|
||||
BUTTON_STYLE
|
||||
+ """
|
||||
QPushButton {
|
||||
background-color: #fd7e14;
|
||||
}
|
||||
@@ -49,8 +57,11 @@ QPushButton:pressed{
|
||||
background-color: #dc680f;
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
UPDATE_BUTTON_STYLE = BUTTON_STYLE + """
|
||||
UPDATE_BUTTON_STYLE = (
|
||||
BUTTON_STYLE
|
||||
+ """
|
||||
QPushButton {
|
||||
background-color: #198754;
|
||||
}
|
||||
@@ -61,8 +72,11 @@ QPushButton:pressed{
|
||||
background-color: #146c43;
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
IMPORT_BUTTON_STYLE = BUTTON_STYLE + """
|
||||
IMPORT_BUTTON_STYLE = (
|
||||
BUTTON_STYLE
|
||||
+ """
|
||||
QPushButton {
|
||||
background-color: #6f42c1;
|
||||
}
|
||||
@@ -73,8 +87,11 @@ QPushButton:pressed{
|
||||
background-color: #4a2d8e;
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
EXPORT_BUTTON_STYLE = BUTTON_STYLE + """
|
||||
EXPORT_BUTTON_STYLE = (
|
||||
BUTTON_STYLE
|
||||
+ """
|
||||
QPushButton {
|
||||
background-color: #20c997;
|
||||
}
|
||||
@@ -85,3 +102,4 @@ QPushButton:pressed{
|
||||
background-color: #198b6d;
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
+34
-13
@@ -1,14 +1,30 @@
|
||||
# coding:utf-8
|
||||
import os
|
||||
import sys
|
||||
|
||||
from PySide6.QtCore import QUrl, QSize, QEventLoop, QTimer
|
||||
from PySide6.QtGui import QIcon, QDesktopServices
|
||||
from qfluentwidgets import (NavigationItemPosition, MessageBox, NavigationAvatarWidget, SplitFluentWindow, SplashScreen)
|
||||
from PySide6.QtWidgets import QApplication
|
||||
from qfluentwidgets import (
|
||||
NavigationItemPosition,
|
||||
MessageBox,
|
||||
NavigationAvatarWidget,
|
||||
SplitFluentWindow,
|
||||
SplashScreen,
|
||||
)
|
||||
from qfluentwidgets import FluentIcon as FIF
|
||||
|
||||
from .students.student_interface import StudentInterface
|
||||
from .video.video_interface import VideoInterface
|
||||
from .camera.camera_interface import CameraInterface
|
||||
from .web_view.web_view_interface import WebViewInterface
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
|
||||
def resource_path(relative_path):
|
||||
"""获取资源的绝对路径"""
|
||||
if hasattr(sys, "_MEIPASS"):
|
||||
return os.path.join(sys._MEIPASS, relative_path) # type: ignore
|
||||
return os.path.join(os.path.abspath("."), relative_path)
|
||||
|
||||
|
||||
class Window(SplitFluentWindow):
|
||||
@@ -21,18 +37,20 @@ class Window(SplitFluentWindow):
|
||||
self.web_view_interface = WebViewInterface(self)
|
||||
self.initNavigation()
|
||||
self.resize(900, 700)
|
||||
self.setWindowIcon(QIcon('./resources/images/logo.png'))
|
||||
|
||||
# 使用资源路径函数
|
||||
self.setWindowIcon(QIcon(resource_path("./resources/images/logo.png")))
|
||||
|
||||
desktop = QApplication.screens()[0].availableGeometry()
|
||||
w, h = desktop.width(), desktop.height()
|
||||
self.move(w//2 - self.width()//2, h//2 - self.height()//2)
|
||||
self.move(w // 2 - self.width() // 2, h // 2 - self.height() // 2)
|
||||
|
||||
self.splashScreen = SplashScreen(self.windowIcon(), self)
|
||||
self.splashScreen.setIconSize(QSize(102, 102))
|
||||
self.show()
|
||||
self.createSubInterface()
|
||||
self.splashScreen.finish()
|
||||
|
||||
|
||||
def createSubInterface(self):
|
||||
loop = QEventLoop(self)
|
||||
QTimer.singleShot(2000, loop.quit)
|
||||
@@ -44,17 +62,20 @@ class Window(SplitFluentWindow):
|
||||
self.addSubInterface(self.camera_interface, FIF.CAMERA, "Camera")
|
||||
self.addSubInterface(self.web_view_interface, FIF.FIT_PAGE, "Web View")
|
||||
|
||||
# 使用资源路径函数
|
||||
self.navigationInterface.addWidget(
|
||||
routeKey="Info",
|
||||
widget=NavigationAvatarWidget('Info', 'resource/images/logo.png'),
|
||||
widget=NavigationAvatarWidget(
|
||||
"Info", resource_path("./resource/images/logo.png")
|
||||
),
|
||||
onClick=self.showMessageBox,
|
||||
position=NavigationItemPosition.BOTTOM,
|
||||
)
|
||||
|
||||
self.navigationInterface.addItem(
|
||||
routeKey='settingInterface',
|
||||
routeKey="settingInterface",
|
||||
icon=FIF.SETTING,
|
||||
text='设置',
|
||||
text="设置",
|
||||
position=NavigationItemPosition.BOTTOM,
|
||||
)
|
||||
|
||||
@@ -62,12 +83,12 @@ class Window(SplitFluentWindow):
|
||||
|
||||
def showMessageBox(self):
|
||||
w = MessageBox(
|
||||
'支持作者🥰',
|
||||
'个人开发不易,如果这个项目帮助到了您,可以考虑请作者喝一瓶快乐水🥤。您的支持就是作者开发和维护项目的动力🚀',
|
||||
self
|
||||
"支持作者🥰",
|
||||
"个人开发不易,如果这个项目帮助到了您,可以考虑请作者喝一瓶快乐水🥤。您的支持就是作者开发和维护项目的动力🚀",
|
||||
self,
|
||||
)
|
||||
w.yesButton.setText('来啦老弟')
|
||||
w.cancelButton.setText('下次一定')
|
||||
w.yesButton.setText("来啦老弟")
|
||||
w.cancelButton.setText("下次一定")
|
||||
|
||||
if w.exec():
|
||||
QDesktopServices.openUrl(QUrl("https://afdian.net/a/zhiyiYo"))
|
||||
|
||||
@@ -18,25 +18,25 @@ class VideoStream(QObject):
|
||||
self.timer.timeout.connect(self.update_frame)
|
||||
self.retry_count = 0
|
||||
self.max_retry = 5
|
||||
|
||||
|
||||
self.open_stream()
|
||||
|
||||
|
||||
def open_stream(self):
|
||||
"""尝试打开RTSP视频流"""
|
||||
if self.cap:
|
||||
self.cap.release()
|
||||
|
||||
|
||||
# 创建VideoCapture对象并设置RTSP参数
|
||||
self.cap = cv2.VideoCapture(self.stream_url)
|
||||
|
||||
|
||||
# 关键设置:使用TCP传输(避免UDP丢包问题),设置超时和缓冲区
|
||||
# self.cap.set(cv2.CAP_PROP_RTSP_TRANSPORT, 1) # 1 = TCP传输模式
|
||||
self.cap.set(cv2.CAP_PROP_OPEN_TIMEOUT_MSEC, 5000) # 5秒超时
|
||||
self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) # 最小化缓冲区
|
||||
|
||||
|
||||
# 打开RTSP流
|
||||
self.cap.open(self.stream_url)
|
||||
|
||||
|
||||
if not self.cap.isOpened():
|
||||
self.retry_count += 1
|
||||
if self.retry_count <= self.max_retry:
|
||||
@@ -46,11 +46,11 @@ class VideoStream(QObject):
|
||||
else:
|
||||
self.retry_count = 0
|
||||
self.timer.start(30) # 约30fps
|
||||
|
||||
|
||||
def update_frame(self):
|
||||
if not self.cap or not self.cap.isOpened():
|
||||
return
|
||||
|
||||
|
||||
ret, frame = self.cap.read()
|
||||
if ret:
|
||||
# 转换为黑白图像
|
||||
@@ -67,24 +67,25 @@ class VideoStream(QObject):
|
||||
else:
|
||||
# 读取失败时重新连接
|
||||
self.open_stream()
|
||||
|
||||
|
||||
def stop(self):
|
||||
self.timer.stop()
|
||||
if self.cap:
|
||||
self.cap.release()
|
||||
|
||||
|
||||
class CameraInterface(QWidget):
|
||||
def __init__(self, rtsp_url="rtsp://127.0.0.1:8554/stream"):
|
||||
super().__init__()
|
||||
self.setObjectName("camera_interface")
|
||||
self.setWindowTitle("RTSP视频流接收器")
|
||||
self.resize(800, 600)
|
||||
|
||||
|
||||
# 创建UI
|
||||
self.label = QLabel("等待RTSP视频流...")
|
||||
self.label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self.label.setStyleSheet("font-size: 20px; color: #888;")
|
||||
|
||||
|
||||
# 创建按钮
|
||||
self.listen_button = PushButton("开始监听RTSP")
|
||||
self.listen_button.clicked.connect(lambda: self.start_listen(rtsp_url))
|
||||
@@ -94,7 +95,7 @@ class CameraInterface(QWidget):
|
||||
layout.addWidget(self.label)
|
||||
layout.addWidget(self.listen_button)
|
||||
self.setLayout(layout)
|
||||
|
||||
|
||||
def start_listen(self, rtsp_url):
|
||||
self.listen_button.setEnabled(False)
|
||||
self.label.setText(f"正在连接: {rtsp_url}...")
|
||||
@@ -102,35 +103,37 @@ class CameraInterface(QWidget):
|
||||
self.stream = VideoStream(rtsp_url)
|
||||
self.stream.new_frame.connect(self.display_frame)
|
||||
self.stream.error_occurred.connect(self.display_error)
|
||||
|
||||
|
||||
def display_frame(self, image):
|
||||
pixmap = QPixmap.fromImage(image)
|
||||
self.label.setPixmap(pixmap.scaled(
|
||||
self.label.width(),
|
||||
self.label.height(),
|
||||
Qt.AspectRatioMode.KeepAspectRatio,
|
||||
Qt.TransformationMode.SmoothTransformation
|
||||
))
|
||||
|
||||
self.label.setPixmap(
|
||||
pixmap.scaled(
|
||||
self.label.width(),
|
||||
self.label.height(),
|
||||
Qt.AspectRatioMode.KeepAspectRatio,
|
||||
Qt.TransformationMode.SmoothTransformation,
|
||||
)
|
||||
)
|
||||
|
||||
def display_error(self, message):
|
||||
QMessageBox.critical(self, "RTSP视频流错误", message)
|
||||
self.label.setText(f"错误: {message}")
|
||||
self.listen_button.setEnabled(True) # 出错时重新启用按钮
|
||||
|
||||
|
||||
def closeEvent(self, event):
|
||||
if hasattr(self, 'stream') and self.stream:
|
||||
if hasattr(self, "stream") and self.stream:
|
||||
self.stream.stop()
|
||||
super().closeEvent(event)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
|
||||
# 使用命令行参数指定RTSP URL
|
||||
rtsp_url = "rtsp://127.0.0.1:8554/stream" # 默认URL
|
||||
if len(sys.argv) > 1:
|
||||
rtsp_url = sys.argv[1]
|
||||
|
||||
|
||||
player = CameraInterface(rtsp_url)
|
||||
player.show()
|
||||
sys.exit(app.exec())
|
||||
|
||||
@@ -14,7 +14,7 @@ class BaseStudentDilalog(MessageBoxBase):
|
||||
self.titleLabel = SubtitleLabel(self.title, self)
|
||||
self.viewLayout.addWidget(self.titleLabel)
|
||||
self.viewLayout.setAlignment(self.titleLabel, Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
|
||||
grid_layout = QGridLayout()
|
||||
self.viewLayout.addLayout(grid_layout)
|
||||
|
||||
@@ -48,8 +48,8 @@ class BaseStudentDilalog(MessageBoxBase):
|
||||
grid_layout.setColumnStretch(1, 1)
|
||||
grid_layout.setAlignment(Qt.AlignmentFlag.AlignLeft)
|
||||
|
||||
self.yesButton.setText('确定')
|
||||
self.cancelButton.setText('取消')
|
||||
self.yesButton.setText("确定")
|
||||
self.cancelButton.setText("取消")
|
||||
|
||||
for widget in [field[1] for field in fields]:
|
||||
widget.setMinimumWidth(200)
|
||||
@@ -64,4 +64,4 @@ class AddStudentDilalog(BaseStudentDilalog):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__("添加学生", parent)
|
||||
self.student_id = None
|
||||
self.yesButton.setText('添加')
|
||||
self.yesButton.setText("添加")
|
||||
|
||||
@@ -1,12 +1,27 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
from PySide6.QtGui import QIcon
|
||||
from PySide6.QtCore import Slot
|
||||
from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QHeaderView, QCheckBox, QTableWidgetItem
|
||||
from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
QVBoxLayout,
|
||||
QHBoxLayout,
|
||||
QHeaderView,
|
||||
QCheckBox,
|
||||
QTableWidgetItem,
|
||||
)
|
||||
|
||||
from queue_sqlite.model import MessageItem
|
||||
|
||||
from qfluentwidgets import CardWidget, PushButton, SearchLineEdit, TableWidget, setCustomStyleSheet
|
||||
from qfluentwidgets import (
|
||||
CardWidget,
|
||||
PushButton,
|
||||
SearchLineEdit,
|
||||
TableWidget,
|
||||
setCustomStyleSheet,
|
||||
)
|
||||
|
||||
from .student_dilalog import AddStudentDilalog
|
||||
from ...style.button_style import ADD_BUTTON_STYLE, BATCH_DELETE_BUTTON_STYLE
|
||||
@@ -15,13 +30,20 @@ from ...signal import listen_signals
|
||||
from ...scheduler_manager import scheduler
|
||||
|
||||
|
||||
def resource_path(relative_path):
|
||||
"""获取资源的绝对路径"""
|
||||
if hasattr(sys, "_MEIPASS"):
|
||||
return os.path.join(sys._MEIPASS, relative_path) # type: ignore
|
||||
return os.path.join(os.path.abspath("."), relative_path)
|
||||
|
||||
|
||||
class StudentInterface(QWidget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setObjectName("student_interface")
|
||||
self.students = []
|
||||
self.setup_ui()
|
||||
|
||||
|
||||
self.create_signal_slot()
|
||||
self.load_data()
|
||||
self.populate_table(self.students)
|
||||
@@ -34,19 +56,21 @@ class StudentInterface(QWidget):
|
||||
# 设置 title
|
||||
self.setWindowTitle("学生管理")
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setContentsMargins(20, 50, 20, 20) # (左, 上, 右, 下)
|
||||
layout.setContentsMargins(20, 50, 20, 20) # (左, 上, 右, 下)
|
||||
card_widget = CardWidget(self)
|
||||
buttons_layout = QHBoxLayout(card_widget)
|
||||
|
||||
self.addButton = PushButton("新增", self)
|
||||
setCustomStyleSheet(self.addButton, ADD_BUTTON_STYLE, ADD_BUTTON_STYLE)
|
||||
self.addButton.clicked.connect(self.add_student)
|
||||
|
||||
|
||||
self.searchInput = SearchLineEdit(self)
|
||||
self.searchInput.setPlaceholderText("搜索学生姓名或学号...")
|
||||
self.searchInput.setFixedWidth(500)
|
||||
self.batchDeleteButton = PushButton("批量删除", self)
|
||||
setCustomStyleSheet(self.batchDeleteButton, BATCH_DELETE_BUTTON_STYLE, BATCH_DELETE_BUTTON_STYLE)
|
||||
setCustomStyleSheet(
|
||||
self.batchDeleteButton, BATCH_DELETE_BUTTON_STYLE, BATCH_DELETE_BUTTON_STYLE
|
||||
)
|
||||
|
||||
buttons_layout.addWidget(self.addButton)
|
||||
buttons_layout.addWidget(self.searchInput)
|
||||
@@ -56,25 +80,89 @@ class StudentInterface(QWidget):
|
||||
layout.addWidget(card_widget)
|
||||
|
||||
self.table_widget = TableWidget(self)
|
||||
self.table_widget.setBorderRadius(8) # 设置边框圆角
|
||||
self.table_widget.setBorderVisible(True) # 设置表格边框可见
|
||||
table_labels = ["", "学生ID", "姓名", "学号", "性别", "班级", "语文", "数学", "英语", "总分", "操作"]
|
||||
self.table_widget.setBorderRadius(8) # 设置边框圆角
|
||||
self.table_widget.setBorderVisible(True) # 设置表格边框可见
|
||||
table_labels = [
|
||||
"",
|
||||
"学生ID",
|
||||
"姓名",
|
||||
"学号",
|
||||
"性别",
|
||||
"班级",
|
||||
"语文",
|
||||
"数学",
|
||||
"英语",
|
||||
"总分",
|
||||
"操作",
|
||||
]
|
||||
self.table_widget.setColumnCount(len(table_labels))
|
||||
self.table_widget.setHorizontalHeaderLabels(table_labels)
|
||||
self.table_widget.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
|
||||
self.table_widget.horizontalHeader().setSectionResizeMode(
|
||||
QHeaderView.ResizeMode.Stretch
|
||||
)
|
||||
|
||||
layout.addWidget(self.table_widget)
|
||||
|
||||
self.setStyleSheet("StudentInterface {background: white}")
|
||||
self.setWindowIcon(QIcon('./resources/images/logo.png'))
|
||||
self.setWindowIcon(QIcon(resource_path("./resources/images/logo.png")))
|
||||
|
||||
def load_data(self):
|
||||
self.students = [
|
||||
{"student_id": 1, "student_name": "张三", "student_number": "2024010101", "gender": 1, "class_name": "1班", "chinese_score": 80, "math_score": 85, "english_score": 90, "total_score": 265},
|
||||
{"student_id": 1, "student_name": "张三", "student_number": "2024010101", "gender": 1, "class_name": "1班", "chinese_score": 80, "math_score": 85, "english_score": 90, "total_score": 265},
|
||||
{"student_id": 1, "student_name": "张三", "student_number": "2024010101", "gender": 1, "class_name": "1班", "chinese_score": 80, "math_score": 85, "english_score": 90, "total_score": 265},
|
||||
{"student_id": 1, "student_name": "张三", "student_number": "2024010101", "gender": 1, "class_name": "1班", "chinese_score": 80, "math_score": 85, "english_score": 90, "total_score": 265},
|
||||
{"student_id": 1, "student_name": "张三", "student_number": "2024010101", "gender": 1, "class_name": "1班", "chinese_score": 80, "math_score": 85, "english_score": 90, "total_score": 265},
|
||||
{
|
||||
"student_id": 1,
|
||||
"student_name": "张三",
|
||||
"student_number": "2024010101",
|
||||
"gender": 1,
|
||||
"class_name": "1班",
|
||||
"chinese_score": 80,
|
||||
"math_score": 85,
|
||||
"english_score": 90,
|
||||
"total_score": 265,
|
||||
},
|
||||
{
|
||||
"student_id": 1,
|
||||
"student_name": "张三",
|
||||
"student_number": "2024010101",
|
||||
"gender": 1,
|
||||
"class_name": "1班",
|
||||
"chinese_score": 80,
|
||||
"math_score": 85,
|
||||
"english_score": 90,
|
||||
"total_score": 265,
|
||||
},
|
||||
{
|
||||
"student_id": 1,
|
||||
"student_name": "张三",
|
||||
"student_number": "2024010101",
|
||||
"gender": 1,
|
||||
"class_name": "1班",
|
||||
"chinese_score": 80,
|
||||
"math_score": 85,
|
||||
"english_score": 90,
|
||||
"total_score": 265,
|
||||
},
|
||||
{
|
||||
"student_id": 1,
|
||||
"student_name": "张三",
|
||||
"student_number": "2024010101",
|
||||
"gender": 1,
|
||||
"class_name": "1班",
|
||||
"chinese_score": 80,
|
||||
"math_score": 85,
|
||||
"english_score": 90,
|
||||
"total_score": 265,
|
||||
},
|
||||
{
|
||||
"student_id": 1,
|
||||
"student_name": "张三",
|
||||
"student_number": "2024010101",
|
||||
"gender": 1,
|
||||
"class_name": "1班",
|
||||
"chinese_score": 80,
|
||||
"math_score": 85,
|
||||
"english_score": 90,
|
||||
"total_score": 265,
|
||||
},
|
||||
]
|
||||
scheduler.update_listen_data("students", json.dumps(self.students))
|
||||
|
||||
@@ -94,7 +182,7 @@ class StudentInterface(QWidget):
|
||||
if key == "gender":
|
||||
value = "男" if value == 1 else "女" if value == 2 else "未知"
|
||||
item = QTableWidgetItem(str(value))
|
||||
self.table_widget.setItem(row, col+1, item)
|
||||
self.table_widget.setItem(row, col + 1, item)
|
||||
|
||||
@Slot(dict)
|
||||
def show_add_student_dialog(self, message_dict: dict):
|
||||
@@ -113,4 +201,7 @@ class StudentInterface(QWidget):
|
||||
|
||||
def add_student(self):
|
||||
self.addButton.setEnabled(False)
|
||||
scheduler.send_message(MessageItem(content={}, destination="add_student"), self.add_student_callback)
|
||||
scheduler.send_message(
|
||||
MessageItem(content={}, destination="add_student"),
|
||||
self.add_student_callback,
|
||||
)
|
||||
|
||||
@@ -9,19 +9,20 @@ class VideoInterface(QWidget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
from PySide6.QtMultimedia import QMediaPlayer
|
||||
|
||||
self.player = QMediaPlayer()
|
||||
self.setObjectName("video_interface")
|
||||
self.setWindowTitle("Fluent Video Player")
|
||||
self.resize(900, 600)
|
||||
|
||||
|
||||
# 设置主题
|
||||
# setTheme(Theme.DARK)
|
||||
|
||||
|
||||
# 创建主布局
|
||||
self.main_layout = QVBoxLayout(self)
|
||||
self.main_layout.setContentsMargins(20, 50, 20, 20)
|
||||
self.main_layout.setSpacing(15)
|
||||
|
||||
|
||||
# 创建标题标签 - 修复字体设置
|
||||
self.title_label = QLabel("Fluent Video Player")
|
||||
self.title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
@@ -30,27 +31,26 @@ class VideoInterface(QWidget):
|
||||
title_font.setPointSize(24)
|
||||
title_font.setBold(True) # 使用setBold方法设置粗体
|
||||
self.title_label.setFont(title_font)
|
||||
|
||||
|
||||
self.main_layout.addWidget(self.title_label)
|
||||
|
||||
|
||||
# 创建视频播放器
|
||||
self.video_widget = VideoWidget()
|
||||
self.video_widget.setMinimumHeight(400)
|
||||
self.main_layout.addWidget(self.video_widget, 1)
|
||||
|
||||
|
||||
# 创建控制面板
|
||||
self.control_layout = QHBoxLayout()
|
||||
self.control_layout.setContentsMargins(10, 10, 10, 10)
|
||||
self.control_layout.setSpacing(15)
|
||||
self.main_layout.addLayout(self.control_layout)
|
||||
|
||||
|
||||
# 添加控制按钮
|
||||
self.open_button = PushButton(FluentIcon.FOLDER_ADD, "打开视频")
|
||||
self.open_button.setFixedWidth(120)
|
||||
self.open_button.clicked.connect(self.open_video)
|
||||
self.control_layout.addWidget(self.open_button)
|
||||
|
||||
|
||||
|
||||
# 添加状态标签 - 修复字体设置
|
||||
self.status_label = QLabel("准备就绪")
|
||||
self.status_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
@@ -58,11 +58,11 @@ class VideoInterface(QWidget):
|
||||
status_font.setPointSize(12)
|
||||
self.status_label.setFont(status_font)
|
||||
self.control_layout.addWidget(self.status_label)
|
||||
|
||||
|
||||
# 设置初始状态
|
||||
self.video_loaded = False
|
||||
self.is_fullscreen = False
|
||||
|
||||
|
||||
# 设置样式
|
||||
# self.setStyleSheet("""
|
||||
# QWidget {
|
||||
@@ -73,38 +73,40 @@ class VideoInterface(QWidget):
|
||||
# padding: 5px;
|
||||
# }
|
||||
# """)
|
||||
|
||||
|
||||
def open_video(self):
|
||||
"""打开视频文件"""
|
||||
file_path, _ = QFileDialog.getOpenFileName(
|
||||
self, "选择视频文件", "",
|
||||
"视频文件 (*.mp4 *.avi *.mkv *.mov *.flv *.wmv);;所有文件 (*.*)"
|
||||
self,
|
||||
"选择视频文件",
|
||||
"",
|
||||
"视频文件 (*.mp4 *.avi *.mkv *.mov *.flv *.wmv);;所有文件 (*.*)",
|
||||
)
|
||||
|
||||
|
||||
if file_path:
|
||||
# 设置视频URL
|
||||
self.video_widget.setVideo(QUrl.fromLocalFile(file_path))
|
||||
self.status_label.setText(f"已加载视频: {file_path.split('/')[-1]}")
|
||||
self.video_loaded = True
|
||||
|
||||
|
||||
def play_video(self):
|
||||
"""播放视频"""
|
||||
if self.video_loaded:
|
||||
self.video_widget.play()
|
||||
self.status_label.setText("正在播放...")
|
||||
|
||||
|
||||
def pause_video(self):
|
||||
"""暂停视频"""
|
||||
if self.video_loaded:
|
||||
self.video_widget.pause()
|
||||
self.status_label.setText("已暂停")
|
||||
|
||||
|
||||
def stop_video(self):
|
||||
"""停止视频"""
|
||||
if self.video_loaded:
|
||||
self.video_widget.stop()
|
||||
self.status_label.setText("已停止")
|
||||
|
||||
|
||||
def toggle_fullscreen(self):
|
||||
"""切换全屏模式"""
|
||||
if self.is_fullscreen:
|
||||
@@ -112,15 +114,15 @@ class VideoInterface(QWidget):
|
||||
else:
|
||||
self.showFullScreen()
|
||||
self.is_fullscreen = True
|
||||
|
||||
|
||||
|
||||
# if __name__ == "__main__":
|
||||
# app = QApplication(sys.argv)
|
||||
|
||||
|
||||
# # 设置应用程序样式
|
||||
# from qfluentwidgets import setThemeColor
|
||||
# setThemeColor('#28a9e0')
|
||||
|
||||
|
||||
# player = VideoPlayer()
|
||||
# player.show()
|
||||
# sys.exit(app.exec())
|
||||
# sys.exit(app.exec())
|
||||
|
||||
+245
-53
@@ -1,41 +1,100 @@
|
||||
from PySide6.QtCore import QObject, Slot, QUrl
|
||||
from PySide6.QtWebChannel import QWebChannel
|
||||
from PySide6.QtWebEngineCore import QWebEngineSettings
|
||||
import time
|
||||
import os
|
||||
import time
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
import json
|
||||
import shutil
|
||||
import base64
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
from PySide6.QtCore import QObject, Slot, QUrl
|
||||
from PySide6.QtWebChannel import QWebChannel
|
||||
from PySide6.QtWebEngineCore import QWebEngineSettings
|
||||
from qframelesswindow.webengine import FramelessWebEngineView
|
||||
from queue_sqlite.model import MessageItem
|
||||
|
||||
from ...scheduler_manager import scheduler
|
||||
from ...signal import global_signals
|
||||
|
||||
|
||||
class WebViewQueueSqliteOperations(QObject):
|
||||
"""
|
||||
用于前端调用QueueSqlite的队列操作
|
||||
"""
|
||||
|
||||
def __init__(self, webview: FramelessWebEngineView, parent=None):
|
||||
super().__init__(parent)
|
||||
self.webview = webview
|
||||
self.page = self.webview.page()
|
||||
global_signals.update_queue_state.connect(self.update_queue_state)
|
||||
|
||||
@Slot(str, str, result=str)
|
||||
def send_message(self, message: str, store_key: str):
|
||||
def callback(result: MessageItem):
|
||||
print(f"send_message callback: {result}")
|
||||
global_signals.update_queue_state.emit(result.to_dict(), store_key)
|
||||
|
||||
scheduler.send_message(MessageItem.from_dict(json.loads(message)), callback)
|
||||
|
||||
def update_queue_state(self, result: dict, store_key: str):
|
||||
json_str = json.dumps(result)
|
||||
print(f"result: {result}")
|
||||
print(f"json_str: {json_str}")
|
||||
js_code = f"""
|
||||
try {{
|
||||
if (typeof window.queueStore.updateQueueData === 'function') {{
|
||||
window.queueStore.updateQueueData('{store_key}', {json_str});
|
||||
}} else {{
|
||||
console.log('updateQueueData function not found');
|
||||
}}
|
||||
}} catch (error) {{
|
||||
console.error('Error in updateQueueData:', error);
|
||||
}}
|
||||
"""
|
||||
print(f"执行的JavaScript代码: {js_code}")
|
||||
self.page.runJavaScript(js_code)
|
||||
|
||||
|
||||
class WebviewBridge(QObject):
|
||||
"""Webview桥接类,用于处理前端和Python后端之间的通信"""
|
||||
|
||||
|
||||
def __init__(self, webview, parent=None):
|
||||
super().__init__(parent)
|
||||
self.webview = webview
|
||||
|
||||
# 启用本地文件访问
|
||||
self.webview.settings().setAttribute(QWebEngineSettings.WebAttribute.LocalContentCanAccessFileUrls, True)
|
||||
self.webview.settings().setAttribute(QWebEngineSettings.WebAttribute.LocalContentCanAccessRemoteUrls, True)
|
||||
self.webview.settings().setAttribute(QWebEngineSettings.WebAttribute.AllowRunningInsecureContent, True)
|
||||
self.webview.settings().setAttribute(QWebEngineSettings.WebAttribute.AllowGeolocationOnInsecureOrigins, True)
|
||||
|
||||
self.webview.settings().setAttribute(
|
||||
QWebEngineSettings.WebAttribute.LocalContentCanAccessFileUrls, True
|
||||
)
|
||||
self.webview.settings().setAttribute(
|
||||
QWebEngineSettings.WebAttribute.LocalContentCanAccessRemoteUrls, True
|
||||
)
|
||||
self.webview.settings().setAttribute(
|
||||
QWebEngineSettings.WebAttribute.AllowRunningInsecureContent, True
|
||||
)
|
||||
self.webview.settings().setAttribute(
|
||||
QWebEngineSettings.WebAttribute.AllowGeolocationOnInsecureOrigins, True
|
||||
)
|
||||
|
||||
# 设置Web安全策略
|
||||
self.webview.page().settings().setAttribute(QWebEngineSettings.WebAttribute.LocalContentCanAccessFileUrls, True)
|
||||
self.webview.page().settings().setAttribute(QWebEngineSettings.WebAttribute.LocalContentCanAccessRemoteUrls, True)
|
||||
self.webview.page().settings().setAttribute(QWebEngineSettings.WebAttribute.AllowRunningInsecureContent, True)
|
||||
|
||||
self.webview.page().settings().setAttribute(
|
||||
QWebEngineSettings.WebAttribute.LocalContentCanAccessFileUrls, True
|
||||
)
|
||||
self.webview.page().settings().setAttribute(
|
||||
QWebEngineSettings.WebAttribute.LocalContentCanAccessRemoteUrls, True
|
||||
)
|
||||
self.webview.page().settings().setAttribute(
|
||||
QWebEngineSettings.WebAttribute.AllowRunningInsecureContent, True
|
||||
)
|
||||
|
||||
# 创建插件目录
|
||||
self.plugins_dir = Path(os.getcwd()) / "plugins"
|
||||
self.plugins_dir.mkdir(exist_ok=True)
|
||||
|
||||
self.plugins_dir = Path("plugins")
|
||||
os.makedirs(self.plugins_dir, exist_ok=True)
|
||||
|
||||
# 插件信息文件
|
||||
self.plugins_info_file = self.plugins_dir / "plugins.json"
|
||||
if not self.plugins_info_file.exists():
|
||||
with open(self.plugins_info_file, 'w') as f:
|
||||
with open(self.plugins_info_file, "w") as f:
|
||||
json.dump([], f)
|
||||
|
||||
@Slot(str, str, str, result=str)
|
||||
@@ -44,55 +103,181 @@ class WebviewBridge(QObject):
|
||||
try:
|
||||
# 将base64编码的文件内容解码并保存为临时文件
|
||||
file_data = base64.b64decode(file_content)
|
||||
|
||||
|
||||
# 创建临时文件来保存上传的ZIP文件
|
||||
with tempfile.NamedTemporaryFile(suffix='.zip', delete=False) as tmp_file:
|
||||
with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as tmp_file:
|
||||
tmp_file.write(file_data)
|
||||
tmp_file_path = tmp_file.name
|
||||
|
||||
|
||||
# 判断插件是否已存在
|
||||
if os.path.exists(self.plugins_dir / name):
|
||||
os.unlink(tmp_file_path)
|
||||
return json.dumps({"success": False, "error": "插件名称已存在"})
|
||||
|
||||
# 解压插件
|
||||
success, error = self.extract_plugin(tmp_file_path, name)
|
||||
success, msg = self.extract_plugin(tmp_file_path, name)
|
||||
|
||||
if not success:
|
||||
# 清理临时文件
|
||||
os.unlink(tmp_file_path)
|
||||
return json.dumps({"success": False, "error": error})
|
||||
|
||||
return json.dumps({"success": False, "error": msg})
|
||||
|
||||
# 读取 plugin.config.json 文件
|
||||
plugin_config_file = msg.replace("index.html", "plugin.config.json")
|
||||
if not os.path.exists(plugin_config_file):
|
||||
os.unlink(tmp_file_path)
|
||||
return json.dumps({"success": False, "error": "插件配置文件不存在"})
|
||||
|
||||
with open(plugin_config_file, "r") as f:
|
||||
plugin_config = json.load(f)
|
||||
|
||||
plugin_conofig_keys = [
|
||||
"pluginName",
|
||||
"pluginId",
|
||||
"pluginDescription",
|
||||
"pluginVersion",
|
||||
"pluginAuthor",
|
||||
]
|
||||
|
||||
for key in plugin_conofig_keys:
|
||||
if key not in plugin_config:
|
||||
os.unlink(tmp_file_path)
|
||||
return json.dumps(
|
||||
{"success": False, "error": "插件配置文件缺少必要字段"}
|
||||
)
|
||||
|
||||
plugin_info = {
|
||||
"id": int(time.time()), # 使用时间戳作为简单ID
|
||||
"id": plugin_config["pluginId"],
|
||||
"name": name,
|
||||
"pluginName": plugin_config["pluginName"],
|
||||
"description": description,
|
||||
"version": "1.0.0",
|
||||
"uploadDate": time.strftime('%Y-%m-%d', time.localtime()),
|
||||
"path": str(self.plugins_dir / name)
|
||||
"version": plugin_config["pluginVersion"],
|
||||
"author": plugin_config["pluginAuthor"],
|
||||
"uploadDate": time.strftime("%Y-%m-%d", time.localtime()),
|
||||
"path": str(self.plugins_dir / name / plugin_config["pluginVersion"]),
|
||||
}
|
||||
|
||||
|
||||
# 创建版本路径并移动插件文件
|
||||
version_path = self.plugins_dir / name / plugin_info["version"]
|
||||
version_path.mkdir(exist_ok=True)
|
||||
for file in os.listdir(os.path.dirname(msg)):
|
||||
shutil.move(os.path.join(os.path.dirname(msg), file), version_path)
|
||||
|
||||
# 读取现有插件信息
|
||||
if self.plugins_info_file.exists():
|
||||
with open(self.plugins_info_file, 'r') as f:
|
||||
if os.path.exists(self.plugins_info_file):
|
||||
with open(self.plugins_info_file, "r") as f:
|
||||
plugins = json.load(f)
|
||||
else:
|
||||
plugins = []
|
||||
|
||||
|
||||
# 判断pluginId是否存在
|
||||
if plugin_info["id"] in [p["id"] for p in plugins]:
|
||||
os.unlink(tmp_file_path)
|
||||
return json.dumps({"success": False, "error": "插件ID已存在"})
|
||||
|
||||
# 添加新插件
|
||||
plugins.append(plugin_info)
|
||||
|
||||
|
||||
# 保存插件信息
|
||||
with open(self.plugins_info_file, 'w') as f:
|
||||
with open(self.plugins_info_file, "w") as f:
|
||||
json.dump(plugins, f)
|
||||
|
||||
|
||||
# 清理临时文件
|
||||
os.unlink(tmp_file_path)
|
||||
|
||||
|
||||
return json.dumps({"success": True})
|
||||
except Exception as e:
|
||||
# 确保任何异常都返回有效的JSON
|
||||
return json.dumps({"success": False, "error": str(e)})
|
||||
return json.dumps({"success": False, "error": str(e)})
|
||||
|
||||
@Slot(str, result=str)
|
||||
def update_plugin(self, plugin_id, name, description, file_content):
|
||||
"""处理插件更新请求"""
|
||||
try:
|
||||
file_data = base64.b64decode(file_content)
|
||||
|
||||
with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as tmp_file:
|
||||
tmp_file.write(file_data)
|
||||
tmp_filepath = tmp_file.name
|
||||
|
||||
success, msg = self.extract_plugin(tmp_filepath, name)
|
||||
|
||||
if not success:
|
||||
os.unlink(tmp_filepath)
|
||||
return json.dumps({"success": False, "error": msg})
|
||||
|
||||
plugin_config_file = msg.replace("index.html", "plugin.config.json")
|
||||
if not os.path.exists(plugin_config_file):
|
||||
os.unlink(tmp_filepath)
|
||||
return json.dumps({"success": False, "error": "插件配置文件不存在"})
|
||||
|
||||
with open(plugin_config_file, "r") as f:
|
||||
plugin_config = json.load(f)
|
||||
|
||||
plugin_config_keys = [
|
||||
"pluginName",
|
||||
"pluginId",
|
||||
"pluginDescription",
|
||||
"pluginVersion",
|
||||
"pluginAuthor",
|
||||
]
|
||||
|
||||
for key in plugin_config_keys:
|
||||
if key not in plugin_config:
|
||||
os.unlink(tmp_filepath)
|
||||
return json.dumps(
|
||||
{"success": False, "error": "插件配置文件缺少必要字段"}
|
||||
)
|
||||
|
||||
# 判断插件是否存在
|
||||
if not os.path.exists(self.plugins_dir / name):
|
||||
os.unlink(tmp_filepath)
|
||||
return json.dumps({"success": False, "error": "插件不存在"})
|
||||
|
||||
# 判断版本是否已存在
|
||||
version_path = self.plugins_dir / name / plugin_config["pluginVersion"]
|
||||
if version_path.exists():
|
||||
os.unlink(tmp_filepath)
|
||||
return json.dumps({"success": False, "error": "版本已存在"})
|
||||
|
||||
# 创建版本路径并移动插件文件
|
||||
version_path.mkdir(exist_ok=True)
|
||||
for file in os.listdir(os.path.dirname(msg)):
|
||||
shutil.move(os.path.join(os.path.dirname(msg), file), version_path)
|
||||
|
||||
# 更新插件信息
|
||||
with open(self.plugins_info_file, "r") as f:
|
||||
plugins = json.load(f)
|
||||
|
||||
plugin_info = next((p for p in plugins if p["id"] == plugin_id), None)
|
||||
if plugin_info:
|
||||
plugin_info["name"] = name
|
||||
plugin_info["description"] = description
|
||||
plugin_info["version"] = [
|
||||
p["version"] for p in plugins if p["id"] == plugin_id
|
||||
][0]
|
||||
plugin_info["uploadDate"] = time.strftime("%Y-%m-%d", time.localtime())
|
||||
plugin_info["path"] = str(
|
||||
self.plugins_dir / name / plugin_config["pluginVersion"]
|
||||
)
|
||||
|
||||
# 保存插件信息
|
||||
with open(self.plugins_info_file, "w") as f:
|
||||
json.dump(plugins, f)
|
||||
|
||||
# 清理临时文件
|
||||
os.unlink(tmp_filepath)
|
||||
|
||||
return json.dumps({"success": True})
|
||||
except Exception as e:
|
||||
# 确保任何异常都返回有效的JSON
|
||||
return json.dumps({"success": False, "error": str(e)})
|
||||
|
||||
# 解压插件
|
||||
def extract_plugin(self, plugin_file, plugin_name):
|
||||
"""解压插件"""
|
||||
try:
|
||||
with zipfile.ZipFile(plugin_file, 'r') as zf:
|
||||
with zipfile.ZipFile(plugin_file, "r") as zf:
|
||||
# 获取插件名称
|
||||
plugin_dir = self.plugins_dir / plugin_name
|
||||
|
||||
@@ -112,8 +297,8 @@ class WebviewBridge(QObject):
|
||||
if not html_files:
|
||||
return False, "插件中未找到HTML文件"
|
||||
index_file = html_files[0]
|
||||
|
||||
return True, ""
|
||||
|
||||
return True, str(index_file.absolute())
|
||||
except Exception as e:
|
||||
return False, str(e)
|
||||
|
||||
@@ -121,8 +306,8 @@ class WebviewBridge(QObject):
|
||||
def list_plugins(self):
|
||||
"""获取插件列表"""
|
||||
try:
|
||||
if self.plugins_info_file.exists():
|
||||
with open(self.plugins_info_file, 'r') as f:
|
||||
if os.path.exists(self.plugins_info_file):
|
||||
with open(self.plugins_info_file, "r") as f:
|
||||
plugins = json.load(f)
|
||||
return json.dumps({"success": True, "plugins": plugins})
|
||||
else:
|
||||
@@ -130,17 +315,18 @@ class WebviewBridge(QObject):
|
||||
except Exception as e:
|
||||
# 确保任何异常都返回有效的JSON
|
||||
return json.dumps({"success": False, "error": str(e)})
|
||||
|
||||
@Slot(int, result=str)
|
||||
|
||||
@Slot(str, result=str)
|
||||
def launch_plugin(self, plugin_id):
|
||||
"""启动插件"""
|
||||
try:
|
||||
if self.plugins_info_file.exists():
|
||||
with open(self.plugins_info_file, 'r') as f:
|
||||
if os.path.exists(self.plugins_info_file):
|
||||
with open(self.plugins_info_file, "r") as f:
|
||||
plugins = json.load(f)
|
||||
|
||||
|
||||
# 查找插件
|
||||
plugin = next((p for p in plugins if p["id"] == plugin_id), None)
|
||||
print(f"Launching plugin: {plugin}")
|
||||
if plugin:
|
||||
# 构建插件URL
|
||||
plugin_path = Path(plugin["path"])
|
||||
@@ -150,10 +336,13 @@ class WebviewBridge(QObject):
|
||||
index_file = html_files[0]
|
||||
url = QUrl.fromLocalFile(str(index_file.absolute()))
|
||||
print(f"Launching plugin: {url.toString()}")
|
||||
print(f"Plugin path: {plugin_path}")
|
||||
self.webview.load(url)
|
||||
return json.dumps({"success": True, "url": url.toString()})
|
||||
else:
|
||||
return json.dumps({"success": False, "error": "插件入口文件未找到"})
|
||||
return json.dumps(
|
||||
{"success": False, "error": "插件入口文件未找到"}
|
||||
)
|
||||
else:
|
||||
return json.dumps({"success": False, "error": "插件未找到"})
|
||||
else:
|
||||
@@ -161,7 +350,7 @@ class WebviewBridge(QObject):
|
||||
except Exception as e:
|
||||
# 确保任何异常都返回有效的JSON
|
||||
return json.dumps({"success": False, "error": str(e)})
|
||||
|
||||
|
||||
@Slot(str, result=str)
|
||||
def save_settings(self, settings_json):
|
||||
"""保存设置"""
|
||||
@@ -173,14 +362,17 @@ class WebviewBridge(QObject):
|
||||
except Exception as e:
|
||||
# 确保任何异常都返回有效的JSON
|
||||
return json.dumps({"success": False, "error": str(e)})
|
||||
|
||||
|
||||
|
||||
class WebviewController(QObject):
|
||||
"""Webview控制器类,负责管理Webview的逻辑"""
|
||||
|
||||
|
||||
def __init__(self, web_engine_page, web_webview, parent=None):
|
||||
super().__init__(parent)
|
||||
self.page = web_engine_page
|
||||
self.bridge = WebviewBridge(web_webview)
|
||||
self.queue_sqlite_operations = WebViewQueueSqliteOperations(web_webview)
|
||||
self.channel = QWebChannel()
|
||||
self.channel.registerObject('py', self.bridge)
|
||||
self.channel.registerObject("entrance", self.bridge)
|
||||
self.channel.registerObject("queueSqlite", self.queue_sqlite_operations)
|
||||
self.page.setWebChannel(self.channel)
|
||||
|
||||
@@ -2,12 +2,21 @@
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
from PySide6.QtWidgets import QFrame, QVBoxLayout, QMenu, QApplication, QWidget, QPushButton, QHBoxLayout
|
||||
from qframelesswindow.webengine import FramelessWebEngineView
|
||||
from PySide6.QtCore import QPoint, Qt, QUrl
|
||||
|
||||
from ...config import config
|
||||
from PySide6.QtWidgets import (
|
||||
QFrame,
|
||||
QVBoxLayout,
|
||||
QMenu,
|
||||
QApplication,
|
||||
QWidget,
|
||||
QPushButton,
|
||||
QHBoxLayout,
|
||||
)
|
||||
from PySide6.QtCore import QPoint, Qt, QUrl
|
||||
from qframelesswindow.webengine import FramelessWebEngineView
|
||||
|
||||
from .entrance import WebviewController
|
||||
from ...config import config
|
||||
|
||||
|
||||
class DevToolsWindow(FramelessWebEngineView):
|
||||
@@ -16,6 +25,7 @@ class DevToolsWindow(FramelessWebEngineView):
|
||||
self.setWindowTitle("开发者工具")
|
||||
self.resize(600, 600)
|
||||
|
||||
|
||||
class DevToolsView(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
@@ -25,6 +35,7 @@ class DevToolsView(QWidget):
|
||||
# 设置窗口大小不可变
|
||||
self.setFixedSize(self.size())
|
||||
|
||||
|
||||
class WebViewInterface(QFrame):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
@@ -33,37 +44,42 @@ class WebViewInterface(QFrame):
|
||||
self.dev_tools_view = None
|
||||
# 添加连接状态标志
|
||||
self.is_local_connection = config.get("project", {}).get("is-production", True)
|
||||
|
||||
|
||||
# 插件目录
|
||||
self.plugins_dir = Path(os.getcwd()) / "plugins"
|
||||
self.plugins_dir.mkdir(exist_ok=True)
|
||||
|
||||
self.webView = FramelessWebEngineView(self)
|
||||
self.html_path = os.getcwd().replace("\\", '/') + '/resources/webview/entrance/dist/index.html'
|
||||
self.remote_url = "http://localhost:8080/"
|
||||
|
||||
self.html_path = (
|
||||
os.getcwd().replace("\\", "/")
|
||||
+ "/resources/webview/entrance/dist/index.html"
|
||||
)
|
||||
self.remote_url = config.get("project", {}).get(
|
||||
"remote-url", "http://localhost:8080/"
|
||||
)
|
||||
|
||||
# 创建WebviewController实例
|
||||
self.page = self.webView.page()
|
||||
self.controller = WebviewController(self.page, self.webView)
|
||||
|
||||
|
||||
# 加载初始页面
|
||||
self.load_current_page()
|
||||
|
||||
|
||||
# 创建导航按钮
|
||||
self.back_button = QPushButton("⬅️", self)
|
||||
self.back_button.clicked.connect(self.webView.back)
|
||||
self.back_button.setFixedSize(40, 30)
|
||||
self.back_button.setEnabled(False)
|
||||
|
||||
|
||||
self.forward_button = QPushButton("➡️", self)
|
||||
self.forward_button.clicked.connect(self.webView.forward)
|
||||
self.forward_button.setFixedSize(40, 30)
|
||||
self.forward_button.setEnabled(False)
|
||||
|
||||
|
||||
self.reload_button = QPushButton("🔃", self)
|
||||
self.reload_button.clicked.connect(self.webView.reload)
|
||||
self.reload_button.setFixedSize(40, 30)
|
||||
|
||||
|
||||
self.home_button = QPushButton("🏠", self)
|
||||
self.home_button.clicked.connect(self.go_home)
|
||||
self.home_button.setFixedSize(40, 30)
|
||||
@@ -87,7 +103,7 @@ class WebViewInterface(QFrame):
|
||||
self.webView.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
||||
self.webView.loadFinished.connect(self.on_load_finished)
|
||||
self.webView.customContextMenuRequested.connect(self.show_context_menu)
|
||||
|
||||
|
||||
# 应用样式
|
||||
self.apply_styles()
|
||||
|
||||
@@ -114,8 +130,10 @@ class WebViewInterface(QFrame):
|
||||
color: #adb5bd;
|
||||
}
|
||||
"""
|
||||
|
||||
nav_button_style = button_style + """
|
||||
|
||||
nav_button_style = (
|
||||
button_style
|
||||
+ """
|
||||
QPushButton {
|
||||
background-color: #6c757d;
|
||||
}
|
||||
@@ -126,8 +144,11 @@ class WebViewInterface(QFrame):
|
||||
background-color: #495057;
|
||||
}
|
||||
"""
|
||||
|
||||
toggle_button_style = button_style + """
|
||||
)
|
||||
|
||||
toggle_button_style = (
|
||||
button_style
|
||||
+ """
|
||||
QPushButton {
|
||||
background-color: #0d6efd;
|
||||
}
|
||||
@@ -138,7 +159,8 @@ class WebViewInterface(QFrame):
|
||||
background-color: #0a58ca;
|
||||
}
|
||||
"""
|
||||
|
||||
)
|
||||
|
||||
self.back_button.setStyleSheet(nav_button_style)
|
||||
self.forward_button.setStyleSheet(nav_button_style)
|
||||
self.reload_button.setStyleSheet(nav_button_style)
|
||||
@@ -178,35 +200,35 @@ class WebViewInterface(QFrame):
|
||||
"""显示自定义右键菜单"""
|
||||
# 创建上下文菜单
|
||||
menu = QMenu(self)
|
||||
|
||||
|
||||
# 添加标准浏览器动作
|
||||
back_action = menu.addAction("后退")
|
||||
back_action.triggered.connect(self.webView.back)
|
||||
|
||||
|
||||
forward_action = menu.addAction("前进")
|
||||
forward_action.triggered.connect(self.webView.forward)
|
||||
|
||||
|
||||
reload_action = menu.addAction("重新加载")
|
||||
reload_action.triggered.connect(self.webView.reload)
|
||||
|
||||
|
||||
menu.addSeparator()
|
||||
|
||||
|
||||
# 添加开发者工具相关动作
|
||||
dev_tools_action = menu.addAction("打开开发者工具")
|
||||
dev_tools_action.triggered.connect(self.toggle_dev_tools)
|
||||
|
||||
|
||||
menu.addSeparator()
|
||||
|
||||
|
||||
menu.addSeparator()
|
||||
|
||||
|
||||
# 在鼠标位置显示菜单
|
||||
menu.exec(self.webView.mapToGlobal(pos))
|
||||
|
||||
|
||||
def toggle_dev_tools(self):
|
||||
"""切换开发者工具窗口显示状态"""
|
||||
if not self.dev_tools_view:
|
||||
self.init_dev_tools()
|
||||
|
||||
|
||||
if self.dev_tools_view is None:
|
||||
return
|
||||
|
||||
@@ -217,97 +239,7 @@ class WebViewInterface(QFrame):
|
||||
self.dev_tools_view.show()
|
||||
self.dev_tools_view.dev_tools_window.show()
|
||||
self.dev_tools_view.dev_tools_window.raise_() # 将窗口置于最前
|
||||
|
||||
def generate_plugins_page(self):
|
||||
"""生成插件列表页面"""
|
||||
plugins = []
|
||||
for item in self.plugins_dir.iterdir():
|
||||
if item.is_dir():
|
||||
index_files = list(item.glob("*.html"))
|
||||
if index_files:
|
||||
plugins.append({
|
||||
'name': item.name,
|
||||
'path': f"file:///{item.absolute()}/{index_files[0].name}",
|
||||
'description': f"插件 {item.name}"
|
||||
})
|
||||
|
||||
# 生成HTML页面
|
||||
html = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>插件管理</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.plugins-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
.plugin-card {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
.plugin-card h3 {
|
||||
margin-top: 0;
|
||||
color: #333;
|
||||
}
|
||||
.plugin-card p {
|
||||
color: #666;
|
||||
}
|
||||
.plugin-card a {
|
||||
display: inline-block;
|
||||
margin-top: 10px;
|
||||
padding: 8px 16px;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.plugin-card a:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>插件管理</h1>
|
||||
<p>当前可用插件列表</p>
|
||||
</div>
|
||||
<div class="plugins-grid">
|
||||
"""
|
||||
|
||||
if not plugins:
|
||||
html += '<p>暂无插件,请上传插件。</p>'
|
||||
else:
|
||||
for plugin in plugins:
|
||||
html += f"""
|
||||
<div class="plugin-card">
|
||||
<h3>{plugin['name']}</h3>
|
||||
<p>{plugin['description']}</p>
|
||||
<a href="{plugin['path']}" target="_blank">访问插件</a>
|
||||
</div>
|
||||
"""
|
||||
|
||||
html += """
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
return html
|
||||
|
||||
|
||||
def closeEvent(self, event):
|
||||
"""关闭时清理资源"""
|
||||
# 停止controller中的定时器
|
||||
@@ -322,4 +254,4 @@ if __name__ == "__main__":
|
||||
window = WebViewInterface()
|
||||
window.resize(1000, 700)
|
||||
window.show()
|
||||
sys.exit(app.exec())
|
||||
sys.exit(app.exec())
|
||||
|
||||
Reference in New Issue
Block a user