modify: webview插件入口
This commit is contained in:
@@ -1,90 +0,0 @@
|
||||
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,186 @@
|
||||
from PySide6.QtCore import QObject, Slot, QUrl
|
||||
from PySide6.QtWebChannel import QWebChannel
|
||||
from PySide6.QtWebEngineCore import QWebEngineSettings
|
||||
import time
|
||||
import os
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
import json
|
||||
import shutil
|
||||
import base64
|
||||
import tempfile
|
||||
|
||||
class WebviewBridge(QObject):
|
||||
"""Webview桥接类,用于处理前端和Python后端之间的通信"""
|
||||
|
||||
def __init__(self, webview, parent=None):
|
||||
super().__init__(parent)
|
||||
self.webview = webview
|
||||
|
||||
# 启用本地文件访问
|
||||
self.webview.settings().setAttribute(QWebEngineSettings.WebAttribute.LocalContentCanAccessFileUrls, True)
|
||||
self.webview.settings().setAttribute(QWebEngineSettings.WebAttribute.LocalContentCanAccessRemoteUrls, True)
|
||||
self.webview.settings().setAttribute(QWebEngineSettings.WebAttribute.AllowRunningInsecureContent, True)
|
||||
self.webview.settings().setAttribute(QWebEngineSettings.WebAttribute.AllowGeolocationOnInsecureOrigins, True)
|
||||
|
||||
# 设置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.plugins_dir = Path(os.getcwd()) / "plugins"
|
||||
self.plugins_dir.mkdir(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:
|
||||
json.dump([], f)
|
||||
|
||||
@Slot(str, str, str, result=str)
|
||||
def upload_plugin(self, name, description, file_content):
|
||||
"""处理插件上传请求"""
|
||||
try:
|
||||
# 将base64编码的文件内容解码并保存为临时文件
|
||||
file_data = base64.b64decode(file_content)
|
||||
|
||||
# 创建临时文件来保存上传的ZIP文件
|
||||
with tempfile.NamedTemporaryFile(suffix='.zip', delete=False) as tmp_file:
|
||||
tmp_file.write(file_data)
|
||||
tmp_file_path = tmp_file.name
|
||||
|
||||
# 解压插件
|
||||
success, error = self.extract_plugin(tmp_file_path, name)
|
||||
if not success:
|
||||
# 清理临时文件
|
||||
os.unlink(tmp_file_path)
|
||||
return json.dumps({"success": False, "error": error})
|
||||
|
||||
plugin_info = {
|
||||
"id": int(time.time()), # 使用时间戳作为简单ID
|
||||
"name": name,
|
||||
"description": description,
|
||||
"version": "1.0.0",
|
||||
"uploadDate": time.strftime('%Y-%m-%d', time.localtime()),
|
||||
"path": str(self.plugins_dir / name)
|
||||
}
|
||||
|
||||
# 读取现有插件信息
|
||||
if self.plugins_info_file.exists():
|
||||
with open(self.plugins_info_file, 'r') as f:
|
||||
plugins = json.load(f)
|
||||
else:
|
||||
plugins = []
|
||||
|
||||
# 添加新插件
|
||||
plugins.append(plugin_info)
|
||||
|
||||
# 保存插件信息
|
||||
with open(self.plugins_info_file, 'w') as f:
|
||||
json.dump(plugins, f)
|
||||
|
||||
# 清理临时文件
|
||||
os.unlink(tmp_file_path)
|
||||
|
||||
return json.dumps({"success": True})
|
||||
except Exception as e:
|
||||
# 确保任何异常都返回有效的JSON
|
||||
return json.dumps({"success": False, "error": str(e)})
|
||||
|
||||
# 解压插件
|
||||
def extract_plugin(self, plugin_file, plugin_name):
|
||||
"""解压插件"""
|
||||
try:
|
||||
with zipfile.ZipFile(plugin_file, 'r') as zf:
|
||||
# 获取插件名称
|
||||
plugin_dir = self.plugins_dir / plugin_name
|
||||
|
||||
# 如果插件目录已存在,删除
|
||||
if plugin_dir.exists():
|
||||
shutil.rmtree(plugin_dir)
|
||||
|
||||
# 创建插件目录并解压
|
||||
plugin_dir.mkdir(exist_ok=True)
|
||||
zf.extractall(plugin_dir)
|
||||
|
||||
# 检测是否有 index.html 文件
|
||||
index_file = plugin_dir / "index.html"
|
||||
if not index_file.exists():
|
||||
# 查找任何HTML文件作为入口
|
||||
html_files = list(plugin_dir.glob("*.html"))
|
||||
if not html_files:
|
||||
return False, "插件中未找到HTML文件"
|
||||
index_file = html_files[0]
|
||||
|
||||
return True, ""
|
||||
except Exception as e:
|
||||
return False, str(e)
|
||||
|
||||
@Slot(result=str)
|
||||
def list_plugins(self):
|
||||
"""获取插件列表"""
|
||||
try:
|
||||
if self.plugins_info_file.exists():
|
||||
with open(self.plugins_info_file, 'r') as f:
|
||||
plugins = json.load(f)
|
||||
return json.dumps({"success": True, "plugins": plugins})
|
||||
else:
|
||||
return json.dumps({"success": True, "plugins": []})
|
||||
except Exception as e:
|
||||
# 确保任何异常都返回有效的JSON
|
||||
return json.dumps({"success": False, "error": str(e)})
|
||||
|
||||
@Slot(int, result=str)
|
||||
def launch_plugin(self, plugin_id):
|
||||
"""启动插件"""
|
||||
try:
|
||||
if self.plugins_info_file.exists():
|
||||
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)
|
||||
if plugin:
|
||||
# 构建插件URL
|
||||
plugin_path = Path(plugin["path"])
|
||||
# 查找HTML文件作为入口
|
||||
html_files = list(plugin_path.glob("*.html"))
|
||||
if html_files:
|
||||
index_file = html_files[0]
|
||||
url = QUrl.fromLocalFile(str(index_file.absolute()))
|
||||
print(f"Launching plugin: {url.toString()}")
|
||||
self.webview.load(url)
|
||||
return json.dumps({"success": True, "url": url.toString()})
|
||||
else:
|
||||
return json.dumps({"success": False, "error": "插件入口文件未找到"})
|
||||
else:
|
||||
return json.dumps({"success": False, "error": "插件未找到"})
|
||||
else:
|
||||
return json.dumps({"success": False, "error": "插件信息文件未找到"})
|
||||
except Exception as e:
|
||||
# 确保任何异常都返回有效的JSON
|
||||
return json.dumps({"success": False, "error": str(e)})
|
||||
|
||||
@Slot(str, result=str)
|
||||
def save_settings(self, settings_json):
|
||||
"""保存设置"""
|
||||
try:
|
||||
settings = json.loads(settings_json)
|
||||
# 在实际应用中,这里会保存设置到配置文件
|
||||
print(f"保存设置: {settings}")
|
||||
return json.dumps({"success": True})
|
||||
except Exception as e:
|
||||
# 确保任何异常都返回有效的JSON
|
||||
return json.dumps({"success": False, "error": str(e)})
|
||||
|
||||
class WebviewController(QObject):
|
||||
"""Webview控制器类,负责管理Webview的逻辑"""
|
||||
|
||||
def __init__(self, web_engine_page, web_webview, parent=None):
|
||||
super().__init__(parent)
|
||||
self.page = web_engine_page
|
||||
self.bridge = WebviewBridge(web_webview)
|
||||
self.channel = QWebChannel()
|
||||
self.channel.registerObject('py', self.bridge)
|
||||
self.page.setWebChannel(self.channel)
|
||||
@@ -1,12 +1,13 @@
|
||||
# coding:utf-8
|
||||
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 ...signal.test_webview import WebviewController
|
||||
from .entrance import WebviewController
|
||||
|
||||
|
||||
class DevToolsWindow(FramelessWebEngineView):
|
||||
@@ -32,14 +33,18 @@ class WebViewInterface(QFrame):
|
||||
self.dev_tools_view = None
|
||||
# 添加连接状态标志
|
||||
self.is_local_connection = config.get("project", {}).get("is-production", True)
|
||||
|
||||
# 插件目录
|
||||
self.plugins_dir = Path(os.getcwd()) / "plugins"
|
||||
self.plugins_dir.mkdir(exist_ok=True)
|
||||
|
||||
self.webView = FramelessWebEngineView(self)
|
||||
self.html_path = os.getcwd().replace("\\", '/') + '/resources/webview/test_webview/dist/index.html'
|
||||
self.html_path = os.getcwd().replace("\\", '/') + '/resources/webview/entrance/dist/index.html'
|
||||
self.remote_url = "http://localhost:8080/"
|
||||
|
||||
# 创建WebviewController实例
|
||||
self.page = self.webView.page()
|
||||
self.controller = WebviewController(self.page)
|
||||
self.controller = WebviewController(self.page, self.webView)
|
||||
|
||||
# 加载初始页面
|
||||
self.load_current_page()
|
||||
@@ -192,6 +197,8 @@ class WebViewInterface(QFrame):
|
||||
|
||||
menu.addSeparator()
|
||||
|
||||
menu.addSeparator()
|
||||
|
||||
# 在鼠标位置显示菜单
|
||||
menu.exec(self.webView.mapToGlobal(pos))
|
||||
|
||||
@@ -211,10 +218,100 @@ 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中的定时器
|
||||
self.controller.bridge.timer.stop()
|
||||
# self.controller.bridge.timer.stop()
|
||||
if self.dev_tools_view:
|
||||
self.dev_tools_view.close()
|
||||
super().closeEvent(event)
|
||||
|
||||
Reference in New Issue
Block a user