first commit
This commit is contained in:
+26
@@ -0,0 +1,26 @@
|
|||||||
|
# Python-generated files
|
||||||
|
__pycache__/
|
||||||
|
*.py[oc]
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
wheels/
|
||||||
|
*.egg-info
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
.venv
|
||||||
|
|
||||||
|
# vue.js build files
|
||||||
|
dist/
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# python-build files
|
||||||
|
output/
|
||||||
|
|
||||||
|
# lib
|
||||||
|
lib/
|
||||||
|
|
||||||
|
# cache files
|
||||||
|
cache/
|
||||||
|
|
||||||
|
# nuitka build files
|
||||||
|
nuitka-crash-report.txt
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
3.12
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
|
||||||
|
```bat
|
||||||
|
python -m nuitka --onefile --windows-console-mode=disable --enable-plugin=pyside6 --lto=no --windows-icon-from-ico=.\resources\ico\logo.ico --output-dir=output --remove-output --jobs=4 main.py
|
||||||
|
```
|
||||||
|
|
||||||
|
```bat
|
||||||
|
python -m nuitka ^
|
||||||
|
--onefile ^
|
||||||
|
--windows-console-mode=disable ^
|
||||||
|
--enable-plugin=pyside6 ^
|
||||||
|
--lto=no ^
|
||||||
|
--windows-icon-from-ico=.\resources\ico\logo.ico ^
|
||||||
|
--output-dir=output ^
|
||||||
|
--remove-output ^
|
||||||
|
--jobs=4 ^
|
||||||
|
--include-qt-plugins=mediaservice ^
|
||||||
|
--include-data-dir=.venv/Lib/site-packages/PySide6/plugins=PySide6/plugins ^
|
||||||
|
main.py
|
||||||
|
```
|
||||||
|
|
||||||
|
```bat
|
||||||
|
ffmpeg -re -stream_loop -1 -i test.mp4 -vcodec libx264 -tune zerolatency -f mpegts tcp://127.0.0.1:5000?listen
|
||||||
|
```
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import toml
|
||||||
|
|
||||||
|
config = toml.load("pyproject.toml")
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
from queue_sqlite.scheduler import QueueScheduler
|
||||||
|
from .students.add_student import add_student
|
||||||
|
from .students.students_listen import students
|
||||||
|
|
||||||
|
|
||||||
|
scheduler = QueueScheduler()
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"scheduler",
|
||||||
|
"add_student",
|
||||||
|
"students"
|
||||||
|
]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
from queue_sqlite.mounter.task_mounter import TaskMounter
|
||||||
|
from queue_sqlite.model import MessageItem
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
@TaskMounter.task(meta={"task_name": "add_student"})
|
||||||
|
def add_student(message_item: MessageItem):
|
||||||
|
# create a new instance of the AddStudentDilalog dialog
|
||||||
|
# time.sleep(3)
|
||||||
|
print("点击了添加按钮")
|
||||||
|
return {"message": "学生添加成功"}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
from queue_sqlite.mounter.listen_mounter import ListenMounter
|
||||||
|
from ...signal import listen_signals
|
||||||
|
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))
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
from .global_signals import GlobalSignals
|
||||||
|
from .listen_signals import ListenSignals
|
||||||
|
|
||||||
|
global_signals = GlobalSignals()
|
||||||
|
listen_signals = ListenSignals()
|
||||||
|
|
||||||
|
__all__ = ["global_signals"]
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
from PySide6.QtCore import Signal, QObject
|
||||||
|
|
||||||
|
|
||||||
|
class GlobalSignals(QObject):
|
||||||
|
|
||||||
|
show_add_dialog_signal = Signal(dict)
|
||||||
|
|
||||||
|
__all__ = ["GlobalSignals"]
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
from PySide6.QtCore import Signal, QObject
|
||||||
|
|
||||||
|
|
||||||
|
class ListenSignals(QObject):
|
||||||
|
|
||||||
|
listening_students_signal = Signal(list)
|
||||||
|
|
||||||
|
__all__ = ["ListenSignals"]
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
from PySide6.QtCore import QObject, Slot, Signal, QTimer
|
||||||
|
from PySide6.QtWebChannel import QWebChannel
|
||||||
|
import time
|
||||||
|
|
||||||
|
class WebviewBridge(QObject):
|
||||||
|
"""Webview桥接类,用于处理前端和Python后端之间的通信"""
|
||||||
|
|
||||||
|
# 定义信号
|
||||||
|
time_updated = Signal(str)
|
||||||
|
message_received = Signal(str)
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.frontend_ready = False
|
||||||
|
self.timer = QTimer()
|
||||||
|
self.timer.timeout.connect(self.send_time)
|
||||||
|
|
||||||
|
@Slot()
|
||||||
|
def on_frontend_ready(self):
|
||||||
|
"""当前端准备就绪时调用"""
|
||||||
|
print("Frontend is ready")
|
||||||
|
self.frontend_ready = True
|
||||||
|
# 启动定时器,每秒更新一次时间
|
||||||
|
self.timer.start(1000)
|
||||||
|
|
||||||
|
@Slot(str, result=str)
|
||||||
|
def hello(self, message):
|
||||||
|
"""处理前端发送的消息"""
|
||||||
|
print('Message received from frontend:', message)
|
||||||
|
response = f'Hello from Python: {message}'
|
||||||
|
self.message_received.emit(response)
|
||||||
|
return response
|
||||||
|
|
||||||
|
def send_time(self):
|
||||||
|
"""定期向前端发送时间更新"""
|
||||||
|
if self.frontend_ready:
|
||||||
|
current_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
|
||||||
|
self.time_updated.emit(current_time)
|
||||||
|
return current_time
|
||||||
|
return None
|
||||||
|
|
||||||
|
def set_frontend_ready(self, ready=True):
|
||||||
|
"""设置前端准备状态"""
|
||||||
|
self.frontend_ready = ready
|
||||||
|
|
||||||
|
class WebviewController(QObject):
|
||||||
|
"""Webview控制器类,负责管理Webview的逻辑"""
|
||||||
|
|
||||||
|
def __init__(self, web_engine_page, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.page = web_engine_page
|
||||||
|
self.bridge = WebviewBridge()
|
||||||
|
self.channel = QWebChannel()
|
||||||
|
self.channel.registerObject('py', self.bridge)
|
||||||
|
self.page.setWebChannel(self.channel)
|
||||||
|
|
||||||
|
# 连接bridge的信号
|
||||||
|
self.bridge.time_updated.connect(self.on_time_updated)
|
||||||
|
self.bridge.message_received.connect(self.on_message_received)
|
||||||
|
|
||||||
|
def on_time_updated(self, time_str):
|
||||||
|
"""当bridge发出time_updated信号时调用"""
|
||||||
|
print(f"Time updated: {time_str}")
|
||||||
|
# 调用前端的update_time函数
|
||||||
|
js_code = f"""
|
||||||
|
try {{
|
||||||
|
if (typeof update_time !== 'undefined' && typeof update_time === 'function') {{
|
||||||
|
update_time('{time_str}');
|
||||||
|
}}
|
||||||
|
}} catch (e) {{
|
||||||
|
console.error('Error calling update_time:', e);
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
self.page.runJavaScript(js_code)
|
||||||
|
|
||||||
|
def on_message_received(self, message):
|
||||||
|
"""当bridge发出message_received信号时调用"""
|
||||||
|
print(f"Message received: {message}")
|
||||||
|
# 调用前端的消息处理函数
|
||||||
|
js_code = f"""
|
||||||
|
try {{
|
||||||
|
if (typeof app_sendMessage !== 'undefined' && typeof app_sendMessage === 'function') {{
|
||||||
|
app_sendMessage('{message}');
|
||||||
|
}}
|
||||||
|
}} catch (e) {{
|
||||||
|
console.error('Error calling app_sendMessage:', e);
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
self.page.runJavaScript(js_code)
|
||||||
|
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
BUTTON_STYLE = """
|
||||||
|
QPushButton {
|
||||||
|
border: none;
|
||||||
|
padding: 5px 10px;
|
||||||
|
font-family: 'Segoe UI', 'Microsoft YaHei';
|
||||||
|
font-size: 14px;
|
||||||
|
color: white;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
QPushButton:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
QPushButton:pressed{
|
||||||
|
background-color: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
ADD_BUTTON_STYLE = BUTTON_STYLE + """
|
||||||
|
QPushButton {
|
||||||
|
background-color: #0d6efd;
|
||||||
|
}
|
||||||
|
QPushButton:hover {
|
||||||
|
background-color: #0b5ed7;
|
||||||
|
}
|
||||||
|
QPushButton:pressed{
|
||||||
|
background-color: #0a58ca;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
DELETE_BUTTON_STYLE = BUTTON_STYLE + """
|
||||||
|
QPushButton {
|
||||||
|
background-color: #dc3545;
|
||||||
|
}
|
||||||
|
QPushButton:hover {
|
||||||
|
background-color: #bb2d3b;
|
||||||
|
}
|
||||||
|
QPushButton:pressed{
|
||||||
|
background-color: #b02a37;
|
||||||
|
"""
|
||||||
|
|
||||||
|
BATCH_DELETE_BUTTON_STYLE = BUTTON_STYLE + """
|
||||||
|
QPushButton {
|
||||||
|
background-color: #fd7e14;
|
||||||
|
}
|
||||||
|
QPushButton:hover {
|
||||||
|
background-color: #e96b10;
|
||||||
|
}
|
||||||
|
QPushButton:pressed{
|
||||||
|
background-color: #dc680f;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
UPDATE_BUTTON_STYLE = BUTTON_STYLE + """
|
||||||
|
QPushButton {
|
||||||
|
background-color: #198754;
|
||||||
|
}
|
||||||
|
QPushButton:hover {
|
||||||
|
background-color: #157347;
|
||||||
|
}
|
||||||
|
QPushButton:pressed{
|
||||||
|
background-color: #146c43;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
IMPORT_BUTTON_STYLE = BUTTON_STYLE + """
|
||||||
|
QPushButton {
|
||||||
|
background-color: #6f42c1;
|
||||||
|
}
|
||||||
|
QPushButton:hover {
|
||||||
|
background-color: #5936a2;
|
||||||
|
}
|
||||||
|
QPushButton:pressed{
|
||||||
|
background-color: #4a2d8e;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
EXPORT_BUTTON_STYLE = BUTTON_STYLE + """
|
||||||
|
QPushButton {
|
||||||
|
background-color: #20c997;
|
||||||
|
}
|
||||||
|
QPushButton:hover {
|
||||||
|
background-color: #1aa179;
|
||||||
|
}
|
||||||
|
QPushButton:pressed{
|
||||||
|
background-color: #198b6d;
|
||||||
|
}
|
||||||
|
"""
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
# coding:utf-8
|
||||||
|
from PySide6.QtCore import QUrl, QSize, QEventLoop, QTimer
|
||||||
|
from PySide6.QtGui import QIcon, QDesktopServices
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
class Window(SplitFluentWindow):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.studemt_interface = StudentInterface()
|
||||||
|
self.video_interface = VideoInterface()
|
||||||
|
self.camera_interface = CameraInterface()
|
||||||
|
self.web_view_interface = WebViewInterface(self)
|
||||||
|
self.initNavigation()
|
||||||
|
self.resize(900, 700)
|
||||||
|
self.setWindowIcon(QIcon('./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.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)
|
||||||
|
loop.exec()
|
||||||
|
|
||||||
|
def initNavigation(self):
|
||||||
|
self.addSubInterface(self.studemt_interface, FIF.RINGER, "Students")
|
||||||
|
self.addSubInterface(self.video_interface, FIF.VIDEO, "Video")
|
||||||
|
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'),
|
||||||
|
onClick=self.showMessageBox,
|
||||||
|
position=NavigationItemPosition.BOTTOM,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.navigationInterface.addItem(
|
||||||
|
routeKey='settingInterface',
|
||||||
|
icon=FIF.SETTING,
|
||||||
|
text='设置',
|
||||||
|
position=NavigationItemPosition.BOTTOM,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.navigationInterface.setExpandWidth(280)
|
||||||
|
|
||||||
|
def showMessageBox(self):
|
||||||
|
w = MessageBox(
|
||||||
|
'支持作者🥰',
|
||||||
|
'个人开发不易,如果这个项目帮助到了您,可以考虑请作者喝一瓶快乐水🥤。您的支持就是作者开发和维护项目的动力🚀',
|
||||||
|
self
|
||||||
|
)
|
||||||
|
w.yesButton.setText('来啦老弟')
|
||||||
|
w.cancelButton.setText('下次一定')
|
||||||
|
|
||||||
|
if w.exec():
|
||||||
|
QDesktopServices.openUrl(QUrl("https://afdian.net/a/zhiyiYo"))
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
import sys
|
||||||
|
import cv2
|
||||||
|
from PySide6.QtCore import Qt, QTimer, Signal, QObject
|
||||||
|
from PySide6.QtGui import QImage, QPixmap
|
||||||
|
from PySide6.QtWidgets import QApplication, QLabel, QVBoxLayout, QWidget, QMessageBox
|
||||||
|
from qfluentwidgets import PushButton
|
||||||
|
|
||||||
|
|
||||||
|
class VideoStream(QObject):
|
||||||
|
new_frame = Signal(QImage)
|
||||||
|
error_occurred = Signal(str)
|
||||||
|
|
||||||
|
def __init__(self, rtsp_url):
|
||||||
|
super().__init__()
|
||||||
|
self.stream_url = rtsp_url # 直接使用RTSP URL
|
||||||
|
self.cap = None
|
||||||
|
self.timer = QTimer()
|
||||||
|
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:
|
||||||
|
QTimer.singleShot(2000, self.open_stream) # 2秒后重试
|
||||||
|
else:
|
||||||
|
self.error_occurred.emit(f"无法打开RTSP视频流: {self.stream_url}")
|
||||||
|
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:
|
||||||
|
# 转换为黑白图像
|
||||||
|
# gray_image = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
||||||
|
# h, w = gray_image.shape
|
||||||
|
# qt_image = QImage(gray_image.data, w, h, w, QImage.Format.Format_Grayscale8)
|
||||||
|
|
||||||
|
# 转换为RGB图像
|
||||||
|
rgb_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
||||||
|
h, w, ch = rgb_image.shape
|
||||||
|
qt_image = QImage(rgb_image.data, w, h, w * ch, QImage.Format.Format_RGB888)
|
||||||
|
|
||||||
|
self.new_frame.emit(qt_image)
|
||||||
|
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))
|
||||||
|
|
||||||
|
layout = QVBoxLayout()
|
||||||
|
layout.setContentsMargins(20, 50, 20, 20)
|
||||||
|
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}...")
|
||||||
|
# 创建视频流
|
||||||
|
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
|
||||||
|
))
|
||||||
|
|
||||||
|
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:
|
||||||
|
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())
|
||||||
|
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
from qfluentwidgets import MessageBoxBase, LineEdit, ComboBox, SubtitleLabel
|
||||||
|
from PySide6.QtWidgets import QGridLayout, QLabel
|
||||||
|
from PySide6.QtCore import Qt
|
||||||
|
from PySide6.QtGui import QFont
|
||||||
|
|
||||||
|
|
||||||
|
class BaseStudentDilalog(MessageBoxBase):
|
||||||
|
def __init__(self, title, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.title = title
|
||||||
|
self.setup_ui()
|
||||||
|
|
||||||
|
def setup_ui(self):
|
||||||
|
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)
|
||||||
|
|
||||||
|
self.nameInput = LineEdit(self)
|
||||||
|
self.numberInput = LineEdit(self)
|
||||||
|
self.genderCombo = ComboBox(self)
|
||||||
|
self.genderCombo.addItems(["男", "女"])
|
||||||
|
self.classCombo = ComboBox(self)
|
||||||
|
self.chineseInput = LineEdit(self)
|
||||||
|
self.mathInput = LineEdit(self)
|
||||||
|
self.englishInput = LineEdit(self)
|
||||||
|
|
||||||
|
fields = [
|
||||||
|
("姓名: ", self.nameInput),
|
||||||
|
("学号: ", self.numberInput),
|
||||||
|
("性别: ", self.genderCombo),
|
||||||
|
("班级: ", self.classCombo),
|
||||||
|
("语文: ", self.chineseInput),
|
||||||
|
("数学: ", self.mathInput),
|
||||||
|
("英语: ", self.englishInput),
|
||||||
|
]
|
||||||
|
|
||||||
|
for row, (label_text, widget) in enumerate(fields):
|
||||||
|
label_widget = QLabel(label_text)
|
||||||
|
font = QFont("微软雅黑")
|
||||||
|
font.setBold(True)
|
||||||
|
label_widget.setFont(font)
|
||||||
|
grid_layout.addWidget(label_widget, row, 0)
|
||||||
|
grid_layout.addWidget(widget, row, 1)
|
||||||
|
|
||||||
|
grid_layout.setColumnStretch(1, 1)
|
||||||
|
grid_layout.setAlignment(Qt.AlignmentFlag.AlignLeft)
|
||||||
|
|
||||||
|
self.yesButton.setText('确定')
|
||||||
|
self.cancelButton.setText('取消')
|
||||||
|
|
||||||
|
for widget in [field[1] for field in fields]:
|
||||||
|
widget.setMinimumWidth(200)
|
||||||
|
|
||||||
|
self.nameInput.setFocus()
|
||||||
|
|
||||||
|
self.yesButton.setDefault(False)
|
||||||
|
self.yesButton.setAutoDefault(False)
|
||||||
|
|
||||||
|
|
||||||
|
class AddStudentDilalog(BaseStudentDilalog):
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__("添加学生", parent)
|
||||||
|
self.student_id = None
|
||||||
|
self.yesButton.setText('添加')
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
from PySide6.QtGui import QIcon
|
||||||
|
from PySide6.QtCore import Slot
|
||||||
|
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 .student_dilalog import AddStudentDilalog
|
||||||
|
from ...style.button_style import ADD_BUTTON_STYLE, BATCH_DELETE_BUTTON_STYLE
|
||||||
|
from ...signal import global_signals
|
||||||
|
from ...signal import listen_signals
|
||||||
|
from ...scheduler_manager import scheduler
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
def create_signal_slot(self):
|
||||||
|
global_signals.show_add_dialog_signal.connect(self.show_add_student_dialog)
|
||||||
|
listen_signals.listening_students_signal.connect(self.populate_table)
|
||||||
|
|
||||||
|
def setup_ui(self):
|
||||||
|
# 设置 title
|
||||||
|
self.setWindowTitle("学生管理")
|
||||||
|
layout = QVBoxLayout(self)
|
||||||
|
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)
|
||||||
|
|
||||||
|
buttons_layout.addWidget(self.addButton)
|
||||||
|
buttons_layout.addWidget(self.searchInput)
|
||||||
|
buttons_layout.addStretch(2)
|
||||||
|
buttons_layout.addWidget(self.batchDeleteButton)
|
||||||
|
|
||||||
|
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.setColumnCount(len(table_labels))
|
||||||
|
self.table_widget.setHorizontalHeaderLabels(table_labels)
|
||||||
|
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'))
|
||||||
|
|
||||||
|
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},
|
||||||
|
]
|
||||||
|
scheduler.update_listen_data("students", json.dumps(self.students))
|
||||||
|
|
||||||
|
@Slot(list)
|
||||||
|
def populate_table(self, students: list):
|
||||||
|
print(f"Received students: {students}")
|
||||||
|
self.table_widget.setRowCount(len(students))
|
||||||
|
for row, student_info in enumerate(students):
|
||||||
|
self.setup_table_row(row, student_info)
|
||||||
|
|
||||||
|
def setup_table_row(self, row, student_info):
|
||||||
|
checkbox = QCheckBox()
|
||||||
|
checkbox.setStyleSheet("margin: 10px")
|
||||||
|
self.table_widget.setCellWidget(row, 0, checkbox)
|
||||||
|
for col, key in enumerate(self.students[0].keys()):
|
||||||
|
value = student_info.get(key, "")
|
||||||
|
if key == "gender":
|
||||||
|
value = "男" if value == 1 else "女" if value == 2 else "未知"
|
||||||
|
item = QTableWidgetItem(str(value))
|
||||||
|
self.table_widget.setItem(row, col+1, item)
|
||||||
|
|
||||||
|
@Slot(dict)
|
||||||
|
def show_add_student_dialog(self, message_dict: dict):
|
||||||
|
message = MessageItem.from_dict(message_dict)
|
||||||
|
print(f"Received message: {message}")
|
||||||
|
print("Creating and showing AddStudentDialog")
|
||||||
|
self.addButton.setEnabled(True)
|
||||||
|
add_student_dialog = AddStudentDilalog(self)
|
||||||
|
if add_student_dialog.exec():
|
||||||
|
print("用户点击了添加按钮")
|
||||||
|
else:
|
||||||
|
print("用户点击了取消按钮")
|
||||||
|
|
||||||
|
def add_student_callback(self, message: MessageItem):
|
||||||
|
global_signals.show_add_dialog_signal.emit(message.to_dict())
|
||||||
|
|
||||||
|
def add_student(self):
|
||||||
|
self.addButton.setEnabled(False)
|
||||||
|
scheduler.send_message(MessageItem(content={}, destination="add_student"), self.add_student_callback)
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
from PySide6.QtCore import QUrl, Qt
|
||||||
|
from PySide6.QtGui import QFont # 导入QFont
|
||||||
|
from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel, QFileDialog, QHBoxLayout
|
||||||
|
from qfluentwidgets import setTheme, Theme, FluentIcon, PushButton, PrimaryPushButton
|
||||||
|
from qfluentwidgets.multimedia import VideoWidget
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
# 正确设置字体:使用QFont.Bold表示粗体
|
||||||
|
title_font = QFont()
|
||||||
|
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)
|
||||||
|
status_font = QFont()
|
||||||
|
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 {
|
||||||
|
# background-color: #2b2b2b;
|
||||||
|
# color: #f0f0f0;
|
||||||
|
# }
|
||||||
|
# QLabel {
|
||||||
|
# padding: 5px;
|
||||||
|
# }
|
||||||
|
# """)
|
||||||
|
|
||||||
|
def open_video(self):
|
||||||
|
"""打开视频文件"""
|
||||||
|
file_path, _ = QFileDialog.getOpenFileName(
|
||||||
|
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:
|
||||||
|
self.showNormal()
|
||||||
|
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())
|
||||||
@@ -0,0 +1,228 @@
|
|||||||
|
# coding:utf-8
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
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 ...signal.test_webview import WebviewController
|
||||||
|
|
||||||
|
|
||||||
|
class DevToolsWindow(FramelessWebEngineView):
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent=parent)
|
||||||
|
self.setWindowTitle("开发者工具")
|
||||||
|
self.resize(600, 600)
|
||||||
|
|
||||||
|
class DevToolsView(QWidget):
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent=parent)
|
||||||
|
self.setWindowTitle("开发者工具")
|
||||||
|
self.dev_tools_window = DevToolsWindow(self)
|
||||||
|
self.resize(600, 600)
|
||||||
|
# 设置窗口大小不可变
|
||||||
|
self.setFixedSize(self.size())
|
||||||
|
|
||||||
|
class WebViewInterface(QFrame):
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent=parent)
|
||||||
|
self.setObjectName("webViewInterface")
|
||||||
|
self.dev_tools_view = None
|
||||||
|
# 添加连接状态标志
|
||||||
|
self.is_local_connection = config.get("project", {}).get("is-production", True)
|
||||||
|
|
||||||
|
self.webView = FramelessWebEngineView(self)
|
||||||
|
self.html_path = os.getcwd().replace("\\", '/') + '/resources/webview/test_webview/dist/index.html'
|
||||||
|
self.remote_url = "http://localhost:8080/"
|
||||||
|
|
||||||
|
# 创建WebviewController实例
|
||||||
|
self.page = self.webView.page()
|
||||||
|
self.controller = WebviewController(self.page)
|
||||||
|
|
||||||
|
# 加载初始页面
|
||||||
|
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)
|
||||||
|
|
||||||
|
# 连接WebView的信号以更新按钮状态
|
||||||
|
self.webView.urlChanged.connect(self.update_navigation_buttons)
|
||||||
|
|
||||||
|
# 创建导航按钮布局
|
||||||
|
self.nav_button_layout = QHBoxLayout()
|
||||||
|
self.nav_button_layout.addWidget(self.back_button)
|
||||||
|
self.nav_button_layout.addWidget(self.forward_button)
|
||||||
|
self.nav_button_layout.addWidget(self.reload_button)
|
||||||
|
self.nav_button_layout.addWidget(self.home_button)
|
||||||
|
self.nav_button_layout.addStretch()
|
||||||
|
self.nav_button_layout.setSpacing(5)
|
||||||
|
|
||||||
|
self.vBoxLayout = QVBoxLayout(self)
|
||||||
|
self.vBoxLayout.setContentsMargins(10, 48, 10, 10)
|
||||||
|
self.vBoxLayout.addLayout(self.nav_button_layout)
|
||||||
|
self.vBoxLayout.addWidget(self.webView)
|
||||||
|
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()
|
||||||
|
|
||||||
|
def apply_styles(self):
|
||||||
|
"""应用样式到按钮"""
|
||||||
|
button_style = """
|
||||||
|
QPushButton {
|
||||||
|
border: none;
|
||||||
|
padding: 5px 10px;
|
||||||
|
font-family: 'Segoe UI', 'Microsoft YaHei';
|
||||||
|
font-size: 14px;
|
||||||
|
color: white;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: #2c68c6;
|
||||||
|
}
|
||||||
|
QPushButton:hover {
|
||||||
|
background-color: #3a75d8;
|
||||||
|
}
|
||||||
|
QPushButton:pressed {
|
||||||
|
background-color: #1a56b0;
|
||||||
|
}
|
||||||
|
QPushButton:disabled {
|
||||||
|
background-color: #6c757d;
|
||||||
|
color: #adb5bd;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
nav_button_style = button_style + """
|
||||||
|
QPushButton {
|
||||||
|
background-color: #6c757d;
|
||||||
|
}
|
||||||
|
QPushButton:hover {
|
||||||
|
background-color: #5a6268;
|
||||||
|
}
|
||||||
|
QPushButton:pressed {
|
||||||
|
background-color: #495057;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
toggle_button_style = button_style + """
|
||||||
|
QPushButton {
|
||||||
|
background-color: #0d6efd;
|
||||||
|
}
|
||||||
|
QPushButton:hover {
|
||||||
|
background-color: #0b5ed7;
|
||||||
|
}
|
||||||
|
QPushButton:pressed {
|
||||||
|
background-color: #0a58ca;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.back_button.setStyleSheet(nav_button_style)
|
||||||
|
self.forward_button.setStyleSheet(nav_button_style)
|
||||||
|
self.reload_button.setStyleSheet(nav_button_style)
|
||||||
|
self.home_button.setStyleSheet(nav_button_style)
|
||||||
|
|
||||||
|
def go_home(self):
|
||||||
|
"""返回首页"""
|
||||||
|
self.webView.load(QUrl(self.html_path))
|
||||||
|
|
||||||
|
def update_navigation_buttons(self):
|
||||||
|
"""更新导航按钮状态"""
|
||||||
|
self.back_button.setEnabled(self.webView.history().canGoBack())
|
||||||
|
self.forward_button.setEnabled(self.webView.history().canGoForward())
|
||||||
|
|
||||||
|
def load_current_page(self):
|
||||||
|
"""加载当前连接对应的页面"""
|
||||||
|
if self.is_local_connection:
|
||||||
|
self.webView.load(QUrl(self.html_path))
|
||||||
|
else:
|
||||||
|
self.webView.load(QUrl(self.remote_url))
|
||||||
|
|
||||||
|
def on_load_finished(self, success):
|
||||||
|
"""页面加载完成后初始化开发者工具"""
|
||||||
|
if success:
|
||||||
|
# 预初始化开发者工具
|
||||||
|
self.init_dev_tools()
|
||||||
|
# 更新导航按钮状态
|
||||||
|
self.update_navigation_buttons()
|
||||||
|
|
||||||
|
def init_dev_tools(self):
|
||||||
|
"""初始化开发者工具"""
|
||||||
|
if not self.dev_tools_view:
|
||||||
|
self.dev_tools_view = DevToolsView()
|
||||||
|
self.page.setDevToolsPage(self.dev_tools_view.dev_tools_window.page())
|
||||||
|
|
||||||
|
def show_context_menu(self, pos: QPoint):
|
||||||
|
"""显示自定义右键菜单"""
|
||||||
|
# 创建上下文菜单
|
||||||
|
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.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
|
||||||
|
|
||||||
|
if self.dev_tools_view.dev_tools_window.isVisible():
|
||||||
|
self.dev_tools_view.hide()
|
||||||
|
self.dev_tools_view.dev_tools_window.hide()
|
||||||
|
else:
|
||||||
|
self.dev_tools_view.show()
|
||||||
|
self.dev_tools_view.dev_tools_window.show()
|
||||||
|
self.dev_tools_view.dev_tools_window.raise_() # 将窗口置于最前
|
||||||
|
|
||||||
|
def closeEvent(self, event):
|
||||||
|
"""关闭时清理资源"""
|
||||||
|
# 停止controller中的定时器
|
||||||
|
self.controller.bridge.timer.stop()
|
||||||
|
if self.dev_tools_view:
|
||||||
|
self.dev_tools_view.close()
|
||||||
|
super().closeEvent(event)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
window = WebViewInterface()
|
||||||
|
window.resize(1000, 700)
|
||||||
|
window.show()
|
||||||
|
sys.exit(app.exec())
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from PySide6.QtWidgets import QApplication
|
||||||
|
from app.view import Window
|
||||||
|
from app.scheduler_manager import scheduler
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# # 设置插件路径 - 关键修复
|
||||||
|
# if getattr(sys, 'frozen', False):
|
||||||
|
# # 打包后的路径
|
||||||
|
# base_dir = Path(sys._MEIPASS)
|
||||||
|
# else:
|
||||||
|
# # 开发环境路径
|
||||||
|
# base_dir = Path(__file__).parent
|
||||||
|
|
||||||
|
# # 设置Qt插件路径
|
||||||
|
# os.environ['QT_PLUGIN_PATH'] = str(base_dir / 'plugins')
|
||||||
|
|
||||||
|
# 启动队列调度器
|
||||||
|
scheduler.start_queue_scheduler()
|
||||||
|
|
||||||
|
# 创建Qt应用
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
window = Window()
|
||||||
|
window.show()
|
||||||
|
window.setMicaEffectEnabled(True)
|
||||||
|
|
||||||
|
# 启动事件循环
|
||||||
|
exit_code = app.exec()
|
||||||
|
|
||||||
|
# 停止队列调度器
|
||||||
|
scheduler.stop_queue_scheduler()
|
||||||
|
|
||||||
|
sys.exit(exit_code)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
<?xml version='1.0' encoding='utf8'?>
|
||||||
|
<nuitka-compilation-report nuitka_version="2.7.12" nuitka_commercial_version="not installed" mode="onefile" completion="error exit message (1)" exit_message="pyside6: Error, there is no Qt plugin family 'mediaservice'.">
|
||||||
|
<performance>
|
||||||
|
<memory_usage name="after_launch" value="44781568" />
|
||||||
|
</performance>
|
||||||
|
<data_composer blob_size="None" />
|
||||||
|
<command_line>
|
||||||
|
<option value="--onefile" />
|
||||||
|
<option value="--enable-plugin=pyside6" />
|
||||||
|
<option value="--include-qt-plugins=mediaservice" />
|
||||||
|
<option value="--include-data-dir=.venv/Lib/site-packages/PySide6/plugins=PySide6/plugins" />
|
||||||
|
<option value="--lto=no" />
|
||||||
|
<option value="--windows-icon-from-ico=.\resources\ico\logo.ico" />
|
||||||
|
<option value="--output-dir=output" />
|
||||||
|
<option value="--remove-output" />
|
||||||
|
<option value="--jobs=4" />
|
||||||
|
<option value="main.py" />
|
||||||
|
</command_line>
|
||||||
|
<plugins>
|
||||||
|
<plugin name="anti-bloat" user_enabled="no" />
|
||||||
|
<plugin name="data-files" user_enabled="no" />
|
||||||
|
<plugin name="delvewheel" user_enabled="no" />
|
||||||
|
<plugin name="dll-files" user_enabled="no" />
|
||||||
|
<plugin name="eventlet" user_enabled="no" />
|
||||||
|
<plugin name="gevent" user_enabled="no" />
|
||||||
|
<plugin name="gi" user_enabled="no" />
|
||||||
|
<plugin name="glfw" user_enabled="no" />
|
||||||
|
<plugin name="implicit-imports" user_enabled="no" />
|
||||||
|
<plugin name="kivy" user_enabled="no" />
|
||||||
|
<plugin name="matplotlib" user_enabled="no" />
|
||||||
|
<plugin name="multiprocessing" user_enabled="no" />
|
||||||
|
<plugin name="options-nanny" user_enabled="no" />
|
||||||
|
<plugin name="pbr-compat" user_enabled="no" />
|
||||||
|
<plugin name="pkg-resources" user_enabled="no" />
|
||||||
|
<plugin name="playwright" user_enabled="no" />
|
||||||
|
<plugin name="pyside6" user_enabled="yes" />
|
||||||
|
<plugin name="pywebview" user_enabled="no" />
|
||||||
|
<plugin name="spacy" user_enabled="no" />
|
||||||
|
<plugin name="transformers" user_enabled="no" />
|
||||||
|
</plugins>
|
||||||
|
<onefile cache_mode="temporary" unpack_dir="{TEMP}\onefile_{PID}_{TIME}" />
|
||||||
|
<distributions />
|
||||||
|
<python python_exe="${sys.prefix}\Scripts\python.exe" python_flavor="Unknown" python_version="3.12.9" os_name="Windows" os_release="11" arch_name="x86_64" filesystem_encoding="utf-8">
|
||||||
|
<search_path>
|
||||||
|
<path value="${cwd}" />
|
||||||
|
<path value="${sys.real_prefix}\DLLs" />
|
||||||
|
<path value="${sys.real_prefix}\Lib" />
|
||||||
|
<path value="${sys.real_prefix}" />
|
||||||
|
<path value="${sys.prefix}" />
|
||||||
|
<path value="${sys.prefix}\Lib\site-packages" />
|
||||||
|
<path value="${sys.prefix}\Lib\site-packages\win32" />
|
||||||
|
<path value="${sys.prefix}\Lib\site-packages\win32\lib" />
|
||||||
|
<path value="${sys.prefix}\Lib\site-packages\Pythonwin" />
|
||||||
|
<path value="${sys.prefix}\Lib\site-packages\setuptools\_vendor" />
|
||||||
|
</search_path>
|
||||||
|
</python>
|
||||||
|
<output run_filename="failed too early" />
|
||||||
|
</nuitka-compilation-report>
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
[project]
|
||||||
|
name = "pyside_queue_sqlite"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "A PySide6 application that uses queue-sqlite to manage tasks"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = [
|
||||||
|
"opencv-python>=4.12.0.88",
|
||||||
|
"opencv-python-headless>=4.12.0.88",
|
||||||
|
"pyside6>=6.9.1",
|
||||||
|
"pyside6-fluent-widgets>=1.8.6",
|
||||||
|
"queue-sqlite",
|
||||||
|
"toml>=0.10.2",
|
||||||
|
]
|
||||||
|
# 是否是生产环境
|
||||||
|
is-production = false
|
||||||
|
|
||||||
|
[[tool.uv.index]]
|
||||||
|
default = true
|
||||||
|
# url = "https://pypi.tuna.tsinghua.edu.cn/simple"
|
||||||
|
url = "https://mirrors.aliyun.com/pypi/simple"
|
||||||
|
|
||||||
|
[tool.uv.sources]
|
||||||
|
queue-sqlite = { path = "lib/queue_sqlite" }
|
||||||
|
|
||||||
|
[dependency-groups]
|
||||||
|
dev = [
|
||||||
|
"nuitka>=2.7.12",
|
||||||
|
"pyinstaller>=6.15.0",
|
||||||
|
"zstandard>=0.23.0",
|
||||||
|
]
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
queue-sqlite @ file:///C:/Users/chakcy/Desktop/test_wheel/lib/queue_sqlite-0.1.0-py3-none-any.whl
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
@@ -0,0 +1,23 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/dist
|
||||||
|
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# test_webview
|
||||||
|
|
||||||
|
## Project setup
|
||||||
|
```
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compiles and hot-reloads for development
|
||||||
|
```
|
||||||
|
npm run serve
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compiles and minifies for production
|
||||||
|
```
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lints and fixes files
|
||||||
|
```
|
||||||
|
npm run lint
|
||||||
|
```
|
||||||
|
|
||||||
|
### Customize configuration
|
||||||
|
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
presets: [
|
||||||
|
'@vue/cli-plugin-babel/preset'
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"module": "esnext",
|
||||||
|
"baseUrl": "./",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"src/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"lib": [
|
||||||
|
"esnext",
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"scripthost"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
+12177
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"name": "test_webview",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"serve": "vue-cli-service serve",
|
||||||
|
"build": "vue-cli-service build",
|
||||||
|
"lint": "vue-cli-service lint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"core-js": "^3.8.3",
|
||||||
|
"qwebchannel": "^6.2.0",
|
||||||
|
"vue": "^3.2.13"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.12.16",
|
||||||
|
"@babel/eslint-parser": "^7.12.16",
|
||||||
|
"@vue/cli-plugin-babel": "~5.0.0",
|
||||||
|
"@vue/cli-plugin-eslint": "~5.0.0",
|
||||||
|
"@vue/cli-service": "~5.0.0",
|
||||||
|
"eslint": "^7.32.0",
|
||||||
|
"eslint-plugin-vue": "^8.0.3"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"root": true,
|
||||||
|
"env": {
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"plugin:vue/vue3-essential",
|
||||||
|
"eslint:recommended"
|
||||||
|
],
|
||||||
|
"parserOptions": {
|
||||||
|
"parser": "@babel/eslint-parser"
|
||||||
|
},
|
||||||
|
"rules": {}
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"> 1%",
|
||||||
|
"last 2 versions",
|
||||||
|
"not dead",
|
||||||
|
"not ie 11"
|
||||||
|
]
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
|
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||||
|
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>
|
||||||
|
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||||
|
</noscript>
|
||||||
|
<div id="app"></div>
|
||||||
|
<!-- built files will be auto injected -->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
<template>
|
||||||
|
<div id="app">
|
||||||
|
<div class="container">
|
||||||
|
<div id="sysTime" class="time-display">{{ sysTime }}</div>
|
||||||
|
<div class="message-form">
|
||||||
|
<input v-model="message" id="message" name="message" placeholder="输入消息" />
|
||||||
|
<button @click="sendMessage">发送消息</button>
|
||||||
|
</div>
|
||||||
|
<CustomAlert
|
||||||
|
v-if="alertVisible"
|
||||||
|
:title="alertTitle"
|
||||||
|
:message="alertMessage"
|
||||||
|
@close="alertVisible = false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { QWebChannel } from 'qwebchannel';
|
||||||
|
import CustomAlert from './components/CustomAlert.vue'
|
||||||
|
|
||||||
|
// ... existing code ...
|
||||||
|
export default {
|
||||||
|
name: 'App',
|
||||||
|
components: {
|
||||||
|
CustomAlert
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
window.app_sendMessage = async (data) => {
|
||||||
|
await this.sendMessage(data);
|
||||||
|
};
|
||||||
|
window.update_time = (timeStr) => {
|
||||||
|
this.update_time(timeStr);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
const message = ref('')
|
||||||
|
const sysTime = ref('')
|
||||||
|
const alertVisible = ref(false)
|
||||||
|
const alertTitle = ref('')
|
||||||
|
const alertMessage = ref('')
|
||||||
|
|
||||||
|
let py = null
|
||||||
|
|
||||||
|
// 初始化QWebChannel
|
||||||
|
onMounted(() => {
|
||||||
|
if (window.qt && window.qt.webChannelTransport) {
|
||||||
|
new QWebChannel(window.qt.webChannelTransport, function (channel) {
|
||||||
|
py = channel.objects.py;
|
||||||
|
|
||||||
|
console.log("update_time function registered in global scope");
|
||||||
|
|
||||||
|
// 延迟一小段时间后再通知Python端,确保channel完全建立
|
||||||
|
setTimeout(() => {
|
||||||
|
// 通知Python端前端已经准备就绪
|
||||||
|
if (channel.objects.py && channel.objects.py.on_frontend_ready) {
|
||||||
|
console.log("Calling on_frontend_ready");
|
||||||
|
channel.objects.py.on_frontend_ready();
|
||||||
|
} else {
|
||||||
|
console.log("on_frontend_ready not found");
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 模拟时间更新(当没有Python连接时)
|
||||||
|
setInterval(() => {
|
||||||
|
const currentTime = new Date().toLocaleString('zh-CN');
|
||||||
|
sysTime.value = `当前时间: ${currentTime}`;
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const hello = () => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (py && py.hello) {
|
||||||
|
py.hello(message.value, (res) => {
|
||||||
|
resolve(res);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
resolve(`hello from python: ${message.value}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const sendMessage = async (data = null) => {
|
||||||
|
try {
|
||||||
|
if (typeof data === "string"){
|
||||||
|
console.log("Sending message to Python:", data);
|
||||||
|
showAlert("信息", data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const result = await hello();
|
||||||
|
console.log(result);
|
||||||
|
showAlert("信息", result);
|
||||||
|
} catch (error) {
|
||||||
|
showAlert("错误", "发送消息时发送错误")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const showAlert = (title, message) => {
|
||||||
|
alertTitle.value = title;
|
||||||
|
alertMessage.value = message;
|
||||||
|
alertVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const update_time = function(timeStr) {
|
||||||
|
sysTime.value = `python 本地时间: ${timeStr}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
message,
|
||||||
|
sysTime,
|
||||||
|
alertVisible,
|
||||||
|
alertTitle,
|
||||||
|
alertMessage,
|
||||||
|
sendMessage,
|
||||||
|
showAlert,
|
||||||
|
update_time
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// ... existing code ...
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-display {
|
||||||
|
padding: 5px;
|
||||||
|
border: 2px solid green;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
min-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-form {
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 5px;
|
||||||
|
border: 2px solid green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-form input {
|
||||||
|
padding: 8px;
|
||||||
|
margin-right: 10px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-form button {
|
||||||
|
padding: 8px 16px;
|
||||||
|
background-color: #0078d4;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-form button:hover {
|
||||||
|
background-color: #005a9e;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 6.7 KiB |
@@ -0,0 +1,103 @@
|
|||||||
|
<template>
|
||||||
|
<div class="modal-overlay" @click="closeAlert">
|
||||||
|
<div class="modal-content" @click.stop>
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3>{{ title }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>{{ message }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button @click="closeAlert">确定</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'CustomAlert',
|
||||||
|
props: {
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['close'],
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const closeAlert = () => {
|
||||||
|
emit('close');
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
closeAlert
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 1000;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||||
|
min-width: 300px;
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
padding: 15px;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
border-radius: 5px 5px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
padding: 15px;
|
||||||
|
text-align: right;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer button {
|
||||||
|
padding: 8px 16px;
|
||||||
|
background-color: #0078d4;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer button:hover {
|
||||||
|
background-color: #005a9e;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
<template>
|
||||||
|
<div class="hello">
|
||||||
|
<h1>{{ msg }}</h1>
|
||||||
|
<p>
|
||||||
|
For a guide and recipes on how to configure / customize this project,<br>
|
||||||
|
check out the
|
||||||
|
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
|
||||||
|
</p>
|
||||||
|
<h3>Installed CLI Plugins</h3>
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
|
||||||
|
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
|
||||||
|
</ul>
|
||||||
|
<h3>Essential Links</h3>
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
|
||||||
|
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
|
||||||
|
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
|
||||||
|
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
|
||||||
|
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
|
||||||
|
</ul>
|
||||||
|
<h3>Ecosystem</h3>
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
|
||||||
|
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
|
||||||
|
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
|
||||||
|
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
|
||||||
|
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'HelloWorld',
|
||||||
|
props: {
|
||||||
|
msg: String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||||
|
<style scoped>
|
||||||
|
h3 {
|
||||||
|
margin: 40px 0 0;
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
list-style-type: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
li {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: #42b983;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { createApp } from 'vue'
|
||||||
|
import App from './App.vue'
|
||||||
|
|
||||||
|
createApp(App).mount('#app')
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
const { defineConfig } = require('@vue/cli-service')
|
||||||
|
module.exports = defineConfig({
|
||||||
|
transpileDependencies: true,
|
||||||
|
publicPath: './'
|
||||||
|
})
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
python -m nuitka ^
|
||||||
|
--onefile ^
|
||||||
|
--windows-disable-console ^
|
||||||
|
--enable-plugin=pyside6 ^
|
||||||
|
--include-qt-plugins=multimedia ^
|
||||||
|
--lto=no ^
|
||||||
|
--windows-icon-from-ico=.\resources\ico\logo.ico ^
|
||||||
|
--output-dir=output ^
|
||||||
|
--remove-output ^
|
||||||
|
--jobs=4 ^
|
||||||
|
main.py
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
# 需要遍历的目录
|
||||||
|
root_dir = "./"
|
||||||
|
# 遍历目录
|
||||||
|
for dirpath, dirnames, filenames in os.walk(root_dir):
|
||||||
|
if "__pycache__" in dirnames:
|
||||||
|
# 获取 __pycache__ 目录的全路径
|
||||||
|
pycache_dir = os.path.join(dirpath, "__pycache__")
|
||||||
|
# 删除目录
|
||||||
|
shutil.rmtree(pycache_dir)
|
||||||
|
print(f"Removed: {pycache_dir}")
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def generate_markdown_from_py_files(directory, output_file):
|
||||||
|
with open(output_file, 'w', encoding='utf-8') as md_file:
|
||||||
|
for root, dirs, files in os.walk(directory):
|
||||||
|
# 排除 venv 目录
|
||||||
|
dirs[:] = [d for d in dirs if d != '.venv']
|
||||||
|
dirs[:] = [d for d in dirs if d != '.vscode']
|
||||||
|
dirs[:] = [d for d in dirs if d != 'scripts']
|
||||||
|
dirs[:] = [d for d in dirs if d != 'build']
|
||||||
|
dirs[:] = [d for d in dirs if d != 'lib']
|
||||||
|
for file in files:
|
||||||
|
if file.endswith('.py') or file.endswith('.rs'):
|
||||||
|
file_path = os.path.join(root, file)
|
||||||
|
md_file.write(f'`{file_path}`\n')
|
||||||
|
md_file.write('```python\n')
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as py_file:
|
||||||
|
md_file.write(py_file.read())
|
||||||
|
md_file.write('\n```\n\n')
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# 指定目录和输出文件名
|
||||||
|
target_directory = sys.argv[1] # 替换为你的目标目录
|
||||||
|
output_markdown_file = 'output.md' # 输出的 Markdown 文件名
|
||||||
|
|
||||||
|
generate_markdown_from_py_files(target_directory, output_markdown_file)
|
||||||
|
print(f'Markdown 文件已生成:{output_markdown_file}')
|
||||||
Reference in New Issue
Block a user