modify: h5 插件系统更新
This commit is contained in:
@@ -15,6 +15,7 @@ node_modules/
|
||||
|
||||
# python-build files
|
||||
output/
|
||||
output.md
|
||||
|
||||
# lib
|
||||
lib/
|
||||
@@ -27,3 +28,4 @@ nuitka-crash-report.xml
|
||||
|
||||
# plugin files
|
||||
plugins/
|
||||
|
||||
|
||||
@@ -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": "测试成功"}
|
||||
|
||||
@@ -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"]
|
||||
@@ -5,4 +5,5 @@ class ListenSignals(QObject):
|
||||
|
||||
listening_students_signal = Signal(list)
|
||||
|
||||
|
||||
__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;
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
+33
-12
@@ -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,11 +37,13 @@ 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))
|
||||
@@ -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"))
|
||||
|
||||
@@ -73,6 +73,7 @@ class VideoStream(QObject):
|
||||
if self.cap:
|
||||
self.cap.release()
|
||||
|
||||
|
||||
class CameraInterface(QWidget):
|
||||
def __init__(self, rtsp_url="rtsp://127.0.0.1:8554/stream"):
|
||||
super().__init__()
|
||||
@@ -105,12 +106,14 @@ class CameraInterface(QWidget):
|
||||
|
||||
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)
|
||||
@@ -118,10 +121,11 @@ class CameraInterface(QWidget):
|
||||
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)
|
||||
|
||||
@@ -133,4 +137,3 @@ if __name__ == "__main__":
|
||||
player = CameraInterface(rtsp_url)
|
||||
player.show()
|
||||
sys.exit(app.exec())
|
||||
|
||||
@@ -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,6 +30,13 @@ 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__()
|
||||
@@ -34,7 +56,7 @@ 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)
|
||||
|
||||
@@ -46,7 +68,9 @@ class StudentInterface(QWidget):
|
||||
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,6 +9,7 @@ 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")
|
||||
@@ -50,7 +51,6 @@ class VideoInterface(QWidget):
|
||||
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)
|
||||
@@ -77,8 +77,10 @@ class VideoInterface(QWidget):
|
||||
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:
|
||||
|
||||
+226
-34
@@ -1,14 +1,59 @@
|
||||
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后端之间的通信"""
|
||||
@@ -18,24 +63,38 @@ class WebviewBridge(QObject):
|
||||
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)
|
||||
@@ -46,38 +105,81 @@ class WebviewBridge(QObject):
|
||||
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)
|
||||
|
||||
# 清理临时文件
|
||||
@@ -88,11 +190,94 @@ class WebviewBridge(QObject):
|
||||
# 确保任何异常都返回有效的JSON
|
||||
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
|
||||
|
||||
@@ -113,7 +298,7 @@ class WebviewBridge(QObject):
|
||||
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:
|
||||
@@ -131,16 +316,17 @@ class WebviewBridge(QObject):
|
||||
# 确保任何异常都返回有效的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:
|
||||
@@ -174,6 +363,7 @@ class WebviewBridge(QObject):
|
||||
# 确保任何异常都返回有效的JSON
|
||||
return json.dumps({"success": False, "error": str(e)})
|
||||
|
||||
|
||||
class WebviewController(QObject):
|
||||
"""Webview控制器类,负责管理Webview的逻辑"""
|
||||
|
||||
@@ -181,6 +371,8 @@ class WebviewController(QObject):
|
||||
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):
|
||||
@@ -39,8 +50,13 @@ class WebViewInterface(QFrame):
|
||||
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()
|
||||
@@ -115,7 +131,9 @@ class WebViewInterface(QFrame):
|
||||
}
|
||||
"""
|
||||
|
||||
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,6 +159,7 @@ class WebViewInterface(QFrame):
|
||||
background-color: #0a58ca;
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
self.back_button.setStyleSheet(nav_button_style)
|
||||
self.forward_button.setStyleSheet(nav_button_style)
|
||||
@@ -218,96 +240,6 @@ class WebViewInterface(QFrame):
|
||||
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中的定时器
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
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
|
||||
def resource_path(relative_path):
|
||||
"""获取资源的绝对路径。用于PyInstaller/Nuitka打包后定位资源文件。"""
|
||||
if hasattr(sys, '_MEIPASS'):
|
||||
return os.path.join(sys._MEIPASS, relative_path) # type: ignore
|
||||
return os.path.join(os.path.abspath("."), relative_path)
|
||||
|
||||
# # 设置Qt插件路径
|
||||
# os.environ['QT_PLUGIN_PATH'] = str(base_dir / 'plugins')
|
||||
def main():
|
||||
# 设置基础路径
|
||||
base_dir = os.path.abspath(".")
|
||||
|
||||
# 启动队列调度器
|
||||
scheduler.start_queue_scheduler()
|
||||
|
||||
@@ -14,6 +14,7 @@ dependencies = [
|
||||
]
|
||||
# 是否是生产环境
|
||||
is-production = false
|
||||
remote-url = "http://localhost:8080/"
|
||||
|
||||
[[tool.uv.index]]
|
||||
default = true
|
||||
|
||||
+500
-89
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,8 @@
|
||||
"dependencies": {
|
||||
"core-js": "^3.8.3",
|
||||
"qwebchannel": "^6.2.0",
|
||||
"sass": "^1.92.1",
|
||||
"sass-loader": "^16.0.5",
|
||||
"vue": "^3.2.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"pluginName": "WebView Entrance",
|
||||
"pluginId": "com.webview.entrance",
|
||||
"pluginDescription": "This plugin is used to show the entrance of the webview.",
|
||||
"pluginVersion": "1.0.0",
|
||||
"pluginAuthor": "chakcy"
|
||||
}
|
||||
+1007
-410
File diff suppressed because it is too large
Load Diff
@@ -1,58 +0,0 @@
|
||||
<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,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 @@
|
||||
# new_plugin
|
||||
|
||||
## 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"
|
||||
]
|
||||
}
|
||||
}
|
||||
+12356
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "new_plugin",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "node scripts/copy-plugin-config.js && vue-cli-service serve",
|
||||
"build": "node scripts/copy-plugin-config.js && vue-cli-service build",
|
||||
"lint": "node scripts/copy-plugin-config.js && vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": "^3.8.3",
|
||||
"pinia": "^3.0.3",
|
||||
"pinia-plugin-persistedstate": "^4.5.0",
|
||||
"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,7 @@
|
||||
{
|
||||
"pluginName": "new plugin",
|
||||
"pluginId": "com.example.newplugin",
|
||||
"pluginDescription": "This is a new plugin",
|
||||
"pluginVersion": "1.0.0",
|
||||
"pluginAuthor": "chakcy"
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// 定义源文件和目标文件路径
|
||||
const srcConfigPath = path.resolve(__dirname, '../src/config/plugin.config.json');
|
||||
const publicDirPath = path.resolve(__dirname, '../public');
|
||||
const publicConfigPath = path.resolve(publicDirPath, 'plugin.config.json');
|
||||
|
||||
// 检查源文件是否存在
|
||||
if (fs.existsSync(srcConfigPath)) {
|
||||
// 确保public目录存在
|
||||
if (!fs.existsSync(publicDirPath)) {
|
||||
fs.mkdirSync(publicDirPath, { recursive: true });
|
||||
}
|
||||
|
||||
// 复制文件到public目录
|
||||
fs.copyFileSync(srcConfigPath, publicConfigPath);
|
||||
console.log('Successfully copied plugin.config.json to public directory');
|
||||
} else {
|
||||
console.error('Source plugin.config.json not found at:', srcConfigPath);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<img alt="Vue logo" src="./assets/logo.png">
|
||||
<HelloWorld msg="Welcome to Your Vue.js App"/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HelloWorld from './components/HelloWorld.vue'
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
HelloWorld
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
margin-top: 60px;
|
||||
}
|
||||
</style>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 6.7 KiB |
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2>任务状态</h2>
|
||||
{{ queueStore.queueData }}
|
||||
<div v-if="queueStore.queueData.taskStatus">
|
||||
<p>当前状态: {{ queueStore.queueData.taskStatus }}</p>
|
||||
<p>处理结果: {{ queueStore.queueData.taskResult }}</p>
|
||||
</div>
|
||||
<button @click="sendTask">发送任务</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useQueueStore } from '@/stores/queue'
|
||||
import { onMounted } from 'vue'
|
||||
import { QWebChannel } from 'qwebchannel';
|
||||
|
||||
const queueStore = useQueueStore()
|
||||
|
||||
let queueSqlite = null;
|
||||
|
||||
// 定义全局函数用于更新状态
|
||||
window.updateQueueState = (key, value) => {
|
||||
try {
|
||||
console.log('更新状态:', key, value);
|
||||
queueStore.updateQueueData(key, value);
|
||||
} catch (e) {
|
||||
console.error('更新状态时出错:', e);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (window.qt && window.qt.webChannelTransport) {
|
||||
new QWebChannel(window.qt.webChannelTransport, function (channel) {
|
||||
queueSqlite = channel.objects.queueSqlite;
|
||||
console.log("queueSqlite", queueSqlite)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 这个函数会通过 QWebChannel 调用 Python 端代码
|
||||
const sendTask = () => {
|
||||
// 调用 Python 端的 send_message 方法
|
||||
if (queueSqlite && queueSqlite.send_message) {
|
||||
// 发从 message 以及 pinia store 名称
|
||||
queueSqlite.send_message(
|
||||
JSON.stringify({
|
||||
"content": {"num": 1},
|
||||
"destination": "test"
|
||||
}),
|
||||
"taskStatus"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 当 Python 端执行回调并更新状态时,页面会自动显示最新状态
|
||||
</script>
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"pluginName": "new plugin",
|
||||
"pluginId": "com.example.newplugin",
|
||||
"pluginDescription": "This is a new plugin",
|
||||
"pluginVersion": "1.0.0",
|
||||
"pluginAuthor": "chakcy"
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||
import App from './App.vue'
|
||||
import { useQueueStore } from './stores/queue'
|
||||
|
||||
const app = createApp(App)
|
||||
const pinia = createPinia()
|
||||
pinia.use(piniaPluginPersistedstate)
|
||||
|
||||
app.use(pinia)
|
||||
app.mount('#app')
|
||||
|
||||
window.queueStore = useQueueStore()
|
||||
window.pluginConfig = await fetch('./plugin.config.json').then(res => res.json())
|
||||
console.log('Loaded plugin config:', window.pluginConfig)
|
||||
@@ -0,0 +1,6 @@
|
||||
// src/stores/index.js 或 main.js 中
|
||||
import { createPinia } from 'pinia'
|
||||
|
||||
const pinia = createPinia()
|
||||
|
||||
export default pinia
|
||||
@@ -0,0 +1,18 @@
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useQueueStore = defineStore('queueSqlite', {
|
||||
state: () => ({
|
||||
// 存储队列相关状态
|
||||
queueData: {},
|
||||
// 其他状态...
|
||||
}),
|
||||
persist: true, // 开启持久化
|
||||
|
||||
actions: {
|
||||
updateQueueData(key, value) {
|
||||
this.queueData[key] = value
|
||||
},
|
||||
|
||||
// 其他相关操作...
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,5 @@
|
||||
const { defineConfig } = require('@vue/cli-service')
|
||||
module.exports = defineConfig({
|
||||
transpileDependencies: true,
|
||||
publicPath: './'
|
||||
})
|
||||
@@ -8,4 +8,5 @@ python -m nuitka ^
|
||||
--output-dir=output ^
|
||||
--remove-output ^
|
||||
--jobs=4 ^
|
||||
--output-filename="pyside_queue_sqlite.exe" ^
|
||||
main.py
|
||||
+24
-14
@@ -1,28 +1,38 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def generate_markdown_from_py_files(directory, output_file):
|
||||
with open(output_file, 'w', encoding='utf-8') as md_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']
|
||||
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"]
|
||||
dirs[:] = [d for d in dirs if d != "dist"]
|
||||
dirs[:] = [d for d in dirs if d != "node_modules"]
|
||||
dirs[:] = [d for d in dirs if d != "plugins"]
|
||||
for file in files:
|
||||
if file.endswith('.py') or file.endswith('.rs'):
|
||||
if (
|
||||
file.endswith(".py")
|
||||
or file.endswith(".rs")
|
||||
or file.endswith(".js")
|
||||
or file.endswith(".vue")
|
||||
):
|
||||
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(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')
|
||||
md_file.write("\n```\n\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 指定目录和输出文件名
|
||||
target_directory = sys.argv[1] # 替换为你的目标目录
|
||||
output_markdown_file = 'output.md' # 输出的 Markdown 文件名
|
||||
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}')
|
||||
print(f"Markdown 文件已生成:{output_markdown_file}")
|
||||
|
||||
Reference in New Issue
Block a user