modify: webview插件入口
This commit is contained in:
@@ -24,3 +24,6 @@ cache/
|
||||
|
||||
# nuitka build files
|
||||
nuitka-crash-report.xml
|
||||
|
||||
# plugin files
|
||||
plugins/
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# test_webview
|
||||
# entrance
|
||||
|
||||
## Project setup
|
||||
```
|
||||
Generated
+130
-133
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "test_webview",
|
||||
"name": "entrance",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "test_webview",
|
||||
"name": "entrance",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"core-js": "^3.8.3",
|
||||
@@ -37,20 +37,6 @@
|
||||
"node": "8 || 9 || 10 || 11 || 12 || 13 || 14 || 15 || 16 || 17 || 18 || 19 || 20 || 21 || 22"
|
||||
}
|
||||
},
|
||||
"node_modules/@ampproject/remapping": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.3.0.tgz",
|
||||
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.24"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.27.1.tgz",
|
||||
@@ -67,9 +53,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/compat-data": {
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.28.0.tgz",
|
||||
"integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==",
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.28.4.tgz",
|
||||
"integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -77,22 +63,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/core": {
|
||||
"version": "7.28.3",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.28.3.tgz",
|
||||
"integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==",
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.28.4.tgz",
|
||||
"integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.0",
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/generator": "^7.28.3",
|
||||
"@babel/helper-compilation-targets": "^7.27.2",
|
||||
"@babel/helper-module-transforms": "^7.28.3",
|
||||
"@babel/helpers": "^7.28.3",
|
||||
"@babel/parser": "^7.28.3",
|
||||
"@babel/helpers": "^7.28.4",
|
||||
"@babel/parser": "^7.28.4",
|
||||
"@babel/template": "^7.27.2",
|
||||
"@babel/traverse": "^7.28.3",
|
||||
"@babel/types": "^7.28.2",
|
||||
"@babel/traverse": "^7.28.4",
|
||||
"@babel/types": "^7.28.4",
|
||||
"@jridgewell/remapping": "^2.3.5",
|
||||
"convert-source-map": "^2.0.0",
|
||||
"debug": "^4.1.0",
|
||||
"gensync": "^1.0.0-beta.2",
|
||||
@@ -108,9 +94,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/eslint-parser": {
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/eslint-parser/-/eslint-parser-7.28.0.tgz",
|
||||
"integrity": "sha512-N4ntErOlKvcbTt01rr5wj3y55xnIdx1ymrfIr8C2WnM1Y9glFgWaGDEULJIazOX3XM9NRzhfJ6zZnQ1sBNWU+w==",
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/eslint-parser/-/eslint-parser-7.28.4.tgz",
|
||||
"integrity": "sha512-Aa+yDiH87980jR6zvRfFuCR1+dLb00vBydhTL+zI992Rz/wQhSvuxjmOOuJOgO3XmakO6RykRGD2S1mq1AtgHA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -403,14 +389,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helpers": {
|
||||
"version": "7.28.3",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.28.3.tgz",
|
||||
"integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==",
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.28.4.tgz",
|
||||
"integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.27.2",
|
||||
"@babel/types": "^7.28.2"
|
||||
"@babel/types": "^7.28.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -501,12 +487,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.28.3",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.28.3.tgz",
|
||||
"integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==",
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.28.4.tgz",
|
||||
"integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.28.2"
|
||||
"@babel/types": "^7.28.4"
|
||||
},
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
@@ -811,9 +797,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-block-scoping": {
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.0.tgz",
|
||||
"integrity": "sha512-gKKnwjpdx5sER/wl0WN0efUBFzF/56YZO0RJrSYP4CljXnP31ByY7fol89AzomdlLNzI36AvOTmYHsnZTCkq8Q==",
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.4.tgz",
|
||||
"integrity": "sha512-1yxmvN0MJHOhPVmAsmoW5liWwoILobu/d/ShymZmj867bAdxGbehIrew1DuLpw2Ukv+qDSSPQdYW1dLNE7t11A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -861,9 +847,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-classes": {
|
||||
"version": "7.28.3",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.3.tgz",
|
||||
"integrity": "sha512-DoEWC5SuxuARF2KdKmGUq3ghfPMO6ZzR12Dnp5gubwbeWJo4dbNWXJPVlwvh4Zlq6Z7YVvL8VFxeSOJgjsx4Sg==",
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz",
|
||||
"integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -872,7 +858,7 @@
|
||||
"@babel/helper-globals": "^7.28.0",
|
||||
"@babel/helper-plugin-utils": "^7.27.1",
|
||||
"@babel/helper-replace-supers": "^7.27.1",
|
||||
"@babel/traverse": "^7.28.3"
|
||||
"@babel/traverse": "^7.28.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -1265,9 +1251,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-object-rest-spread": {
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.0.tgz",
|
||||
"integrity": "sha512-9VNGikXxzu5eCiQjdE4IZn8sb9q7Xsk5EXLDBKUYg1e/Tve8/05+KJEtcxGxAgCY5t/BpKQM+JEL/yT4tvgiUA==",
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz",
|
||||
"integrity": "sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1275,7 +1261,7 @@
|
||||
"@babel/helper-plugin-utils": "^7.27.1",
|
||||
"@babel/plugin-transform-destructuring": "^7.28.0",
|
||||
"@babel/plugin-transform-parameters": "^7.27.7",
|
||||
"@babel/traverse": "^7.28.0"
|
||||
"@babel/traverse": "^7.28.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -1402,9 +1388,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-regenerator": {
|
||||
"version": "7.28.3",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.3.tgz",
|
||||
"integrity": "sha512-K3/M/a4+ESb5LEldjQb+XSrpY0nF+ZBFlTCbSnKaYAMfD8v33O6PMs4uYnOk19HlcsI8WMu3McdFPTiQHF/1/A==",
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz",
|
||||
"integrity": "sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1720,9 +1706,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.28.3",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.28.3.tgz",
|
||||
"integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==",
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.28.4.tgz",
|
||||
"integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -1745,18 +1731,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/traverse": {
|
||||
"version": "7.28.3",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.28.3.tgz",
|
||||
"integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==",
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.28.4.tgz",
|
||||
"integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/generator": "^7.28.3",
|
||||
"@babel/helper-globals": "^7.28.0",
|
||||
"@babel/parser": "^7.28.3",
|
||||
"@babel/parser": "^7.28.4",
|
||||
"@babel/template": "^7.27.2",
|
||||
"@babel/types": "^7.28.2",
|
||||
"@babel/types": "^7.28.4",
|
||||
"debug": "^4.3.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1764,9 +1750,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.28.2",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.28.2.tgz",
|
||||
"integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.28.4.tgz",
|
||||
"integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.27.1",
|
||||
@@ -1869,6 +1855,17 @@
|
||||
"@jridgewell/trace-mapping": "^0.3.24"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/remapping": {
|
||||
"version": "2.3.5",
|
||||
"resolved": "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz",
|
||||
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.24"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||
@@ -2198,9 +2195,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/@types/node/-/node-24.3.0.tgz",
|
||||
"integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==",
|
||||
"version": "24.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/@types/node/-/node-24.3.1.tgz",
|
||||
"integrity": "sha512-3vXmQDXy+woz+gnrTvuvNrPzekOi+Ds0ReMxw0LzBiK3a+1k0kQn9f2NWk+lgD4rJehFUmYy2gMhJ2ZI+7YP9g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -2783,53 +2780,53 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@vue/compiler-core": {
|
||||
"version": "3.5.20",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.20.tgz",
|
||||
"integrity": "sha512-8TWXUyiqFd3GmP4JTX9hbiTFRwYHgVL/vr3cqhr4YQ258+9FADwvj7golk2sWNGHR67QgmCZ8gz80nQcMokhwg==",
|
||||
"version": "3.5.21",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.21.tgz",
|
||||
"integrity": "sha512-8i+LZ0vf6ZgII5Z9XmUvrCyEzocvWT+TeR2VBUVlzIH6Tyv57E20mPZ1bCS+tbejgUgmjrEh7q/0F0bibskAmw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.28.3",
|
||||
"@vue/shared": "3.5.20",
|
||||
"@vue/shared": "3.5.21",
|
||||
"entities": "^4.5.0",
|
||||
"estree-walker": "^2.0.2",
|
||||
"source-map-js": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-dom": {
|
||||
"version": "3.5.20",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.20.tgz",
|
||||
"integrity": "sha512-whB44M59XKjqUEYOMPYU0ijUV0G+4fdrHVKDe32abNdX/kJe1NUEMqsi4cwzXa9kyM9w5S8WqFsrfo1ogtBZGQ==",
|
||||
"version": "3.5.21",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.21.tgz",
|
||||
"integrity": "sha512-jNtbu/u97wiyEBJlJ9kmdw7tAr5Vy0Aj5CgQmo+6pxWNQhXZDPsRr1UWPN4v3Zf82s2H3kF51IbzZ4jMWAgPlQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-core": "3.5.20",
|
||||
"@vue/shared": "3.5.20"
|
||||
"@vue/compiler-core": "3.5.21",
|
||||
"@vue/shared": "3.5.21"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-sfc": {
|
||||
"version": "3.5.20",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.20.tgz",
|
||||
"integrity": "sha512-SFcxapQc0/feWiSBfkGsa1v4DOrnMAQSYuvDMpEaxbpH5dKbnEM5KobSNSgU+1MbHCl+9ftm7oQWxvwDB6iBfw==",
|
||||
"version": "3.5.21",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.21.tgz",
|
||||
"integrity": "sha512-SXlyk6I5eUGBd2v8Ie7tF6ADHE9kCR6mBEuPyH1nUZ0h6Xx6nZI29i12sJKQmzbDyr2tUHMhhTt51Z6blbkTTQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.28.3",
|
||||
"@vue/compiler-core": "3.5.20",
|
||||
"@vue/compiler-dom": "3.5.20",
|
||||
"@vue/compiler-ssr": "3.5.20",
|
||||
"@vue/shared": "3.5.20",
|
||||
"@vue/compiler-core": "3.5.21",
|
||||
"@vue/compiler-dom": "3.5.21",
|
||||
"@vue/compiler-ssr": "3.5.21",
|
||||
"@vue/shared": "3.5.21",
|
||||
"estree-walker": "^2.0.2",
|
||||
"magic-string": "^0.30.17",
|
||||
"magic-string": "^0.30.18",
|
||||
"postcss": "^8.5.6",
|
||||
"source-map-js": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-ssr": {
|
||||
"version": "3.5.20",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.20.tgz",
|
||||
"integrity": "sha512-RSl5XAMc5YFUXpDQi+UQDdVjH9FnEpLDHIALg5J0ITHxkEzJ8uQLlo7CIbjPYqmZtt6w0TsIPbo1izYXwDG7JA==",
|
||||
"version": "3.5.21",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.21.tgz",
|
||||
"integrity": "sha512-vKQ5olH5edFZdf5ZrlEgSO1j1DMA4u23TVK5XR1uMhvwnYvVdDF0nHXJUblL/GvzlShQbjhZZ2uvYmDlAbgo9w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.5.20",
|
||||
"@vue/shared": "3.5.20"
|
||||
"@vue/compiler-dom": "3.5.21",
|
||||
"@vue/shared": "3.5.21"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/component-compiler-utils": {
|
||||
@@ -2903,53 +2900,53 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@vue/reactivity": {
|
||||
"version": "3.5.20",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.20.tgz",
|
||||
"integrity": "sha512-hS8l8x4cl1fmZpSQX/NXlqWKARqEsNmfkwOIYqtR2F616NGfsLUm0G6FQBK6uDKUCVyi1YOL8Xmt/RkZcd/jYQ==",
|
||||
"version": "3.5.21",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.21.tgz",
|
||||
"integrity": "sha512-3ah7sa+Cwr9iiYEERt9JfZKPw4A2UlbY8RbbnH2mGCE8NwHkhmlZt2VsH0oDA3P08X3jJd29ohBDtX+TbD9AsA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/shared": "3.5.20"
|
||||
"@vue/shared": "3.5.21"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/runtime-core": {
|
||||
"version": "3.5.20",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.20.tgz",
|
||||
"integrity": "sha512-vyQRiH5uSZlOa+4I/t4Qw/SsD/gbth0SW2J7oMeVlMFMAmsG1rwDD6ok0VMmjXY3eI0iHNSSOBilEDW98PLRKw==",
|
||||
"version": "3.5.21",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.21.tgz",
|
||||
"integrity": "sha512-+DplQlRS4MXfIf9gfD1BOJpk5RSyGgGXD/R+cumhe8jdjUcq/qlxDawQlSI8hCKupBlvM+3eS1se5xW+SuNAwA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "3.5.20",
|
||||
"@vue/shared": "3.5.20"
|
||||
"@vue/reactivity": "3.5.21",
|
||||
"@vue/shared": "3.5.21"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/runtime-dom": {
|
||||
"version": "3.5.20",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.20.tgz",
|
||||
"integrity": "sha512-KBHzPld/Djw3im0CQ7tGCpgRedryIn4CcAl047EhFTCCPT2xFf4e8j6WeKLgEEoqPSl9TYqShc3Q6tpWpz/Xgw==",
|
||||
"version": "3.5.21",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.21.tgz",
|
||||
"integrity": "sha512-3M2DZsOFwM5qI15wrMmNF5RJe1+ARijt2HM3TbzBbPSuBHOQpoidE+Pa+XEaVN+czbHf81ETRoG1ltztP2em8w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "3.5.20",
|
||||
"@vue/runtime-core": "3.5.20",
|
||||
"@vue/shared": "3.5.20",
|
||||
"@vue/reactivity": "3.5.21",
|
||||
"@vue/runtime-core": "3.5.21",
|
||||
"@vue/shared": "3.5.21",
|
||||
"csstype": "^3.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/server-renderer": {
|
||||
"version": "3.5.20",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.20.tgz",
|
||||
"integrity": "sha512-HthAS0lZJDH21HFJBVNTtx+ULcIbJQRpjSVomVjfyPkFSpCwvsPTA+jIzOaUm3Hrqx36ozBHePztQFg6pj5aKg==",
|
||||
"version": "3.5.21",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.21.tgz",
|
||||
"integrity": "sha512-qr8AqgD3DJPJcGvLcJKQo2tAc8OnXRcfxhOJCPF+fcfn5bBGz7VCcO7t+qETOPxpWK1mgysXvVT/j+xWaHeMWA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-ssr": "3.5.20",
|
||||
"@vue/shared": "3.5.20"
|
||||
"@vue/compiler-ssr": "3.5.21",
|
||||
"@vue/shared": "3.5.21"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "3.5.20"
|
||||
"vue": "3.5.21"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/shared": {
|
||||
"version": "3.5.20",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.20.tgz",
|
||||
"integrity": "sha512-SoRGP596KU/ig6TfgkCMbXkr4YJ91n/QSdMuqeP5r3hVIYA3CPHUBCc7Skak0EAKV+5lL4KyIh61VA/pK1CIAA==",
|
||||
"version": "3.5.21",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.21.tgz",
|
||||
"integrity": "sha512-+2k1EQpnYuVuu3N7atWyG3/xoFWIVJZq4Mz8XNOdScFI0etES75fbny/oU4lKWk/577P1zmg0ioYvpGEDZ3DLw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vue/vue-loader-v15": {
|
||||
@@ -3934,9 +3931,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001737",
|
||||
"resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001737.tgz",
|
||||
"integrity": "sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw==",
|
||||
"version": "1.0.30001741",
|
||||
"resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001741.tgz",
|
||||
"integrity": "sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -5229,9 +5226,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.211",
|
||||
"resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.211.tgz",
|
||||
"integrity": "sha512-IGBvimJkotaLzFnwIVgW9/UD/AOJ2tByUmeOrtqBfACSbAw5b1G0XpvdaieKyc7ULmbwXVx+4e4Be8pOPBrYkw==",
|
||||
"version": "1.5.214",
|
||||
"resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.214.tgz",
|
||||
"integrity": "sha512-TpvUNdha+X3ybfU78NoQatKvQEm1oq3lf2QbnmCEdw+Bd9RuIAY+hJTvq1avzHM0f7EJfnH3vbCnbzKzisc/9Q==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
@@ -8243,9 +8240,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.19",
|
||||
"resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.19.tgz",
|
||||
"integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
|
||||
"version": "2.0.20",
|
||||
"resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.20.tgz",
|
||||
"integrity": "sha512-7gK6zSXEH6neM212JgfYFXe+GmZQM+fia5SsusuBIUgnPheLFBmIPhtFoAQRj8/7wASYQnbDlHPVwY0BefoFgA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -10788,14 +10785,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/terser": {
|
||||
"version": "5.43.1",
|
||||
"resolved": "https://registry.npmmirror.com/terser/-/terser-5.43.1.tgz",
|
||||
"integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==",
|
||||
"version": "5.44.0",
|
||||
"resolved": "https://registry.npmmirror.com/terser/-/terser-5.44.0.tgz",
|
||||
"integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"@jridgewell/source-map": "^0.3.3",
|
||||
"acorn": "^8.14.0",
|
||||
"acorn": "^8.15.0",
|
||||
"commander": "^2.20.0",
|
||||
"source-map-support": "~0.5.20"
|
||||
},
|
||||
@@ -11259,16 +11256,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vue": {
|
||||
"version": "3.5.20",
|
||||
"resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.20.tgz",
|
||||
"integrity": "sha512-2sBz0x/wis5TkF1XZ2vH25zWq3G1bFEPOfkBcx2ikowmphoQsPH6X0V3mmPCXA2K1N/XGTnifVyDQP4GfDDeQw==",
|
||||
"version": "3.5.21",
|
||||
"resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.21.tgz",
|
||||
"integrity": "sha512-xxf9rum9KtOdwdRkiApWL+9hZEMWE90FHh8yS1+KJAiWYh+iGWV1FquPjoO9VUHQ+VIhsCXNNyZ5Sf4++RVZBA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.5.20",
|
||||
"@vue/compiler-sfc": "3.5.20",
|
||||
"@vue/runtime-dom": "3.5.20",
|
||||
"@vue/server-renderer": "3.5.20",
|
||||
"@vue/shared": "3.5.20"
|
||||
"@vue/compiler-dom": "3.5.21",
|
||||
"@vue/compiler-sfc": "3.5.21",
|
||||
"@vue/runtime-dom": "3.5.21",
|
||||
"@vue/server-renderer": "3.5.21",
|
||||
"@vue/shared": "3.5.21"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "*"
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "test_webview",
|
||||
"name": "entrance",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,574 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<div class="container">
|
||||
<h1>插件门户</h1>
|
||||
<nav class="navbar">
|
||||
<button @click="showUploadForm" :class="{ active: currentView === 'upload' }">上传插件</button>
|
||||
<button @click="listPlugins" :class="{ active: currentView === 'list' }">插件列表</button>
|
||||
<button @click="showSettings" :class="{ active: currentView === 'settings' }">设置</button>
|
||||
</nav>
|
||||
|
||||
<div class="content">
|
||||
<!-- 上传插件表单 -->
|
||||
<div v-if="currentView === 'upload'" class="upload-section">
|
||||
<h2>上传新插件</h2>
|
||||
<form @submit.prevent="uploadPlugin">
|
||||
<div class="form-group">
|
||||
<label for="pluginName">插件名称:</label>
|
||||
<input type="text" id="pluginName" v-model="pluginForm.name" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="pluginFile">选择插件文件 (ZIP格式):</label>
|
||||
<input type="file" id="pluginFile" @change="handleFileUpload" accept=".zip" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="pluginDescription">插件描述:</label>
|
||||
<textarea id="pluginDescription" v-model="pluginForm.description"></textarea>
|
||||
</div>
|
||||
|
||||
<button type="submit" :disabled="uploading">
|
||||
{{ uploading ? '上传中...' : '上传插件' }}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div v-if="uploadMessage" class="message" :class="uploadMessage.type">
|
||||
{{ uploadMessage.text }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 插件列表 -->
|
||||
<div v-if="currentView === 'list'" class="plugins-section">
|
||||
<h2>可用插件</h2>
|
||||
<div v-if="loading" class="loading">加载中...</div>
|
||||
<div v-else-if="plugins.length === 0" class="no-plugins">
|
||||
暂无插件,请先上传插件。
|
||||
</div>
|
||||
<div v-else class="plugins-grid">
|
||||
<div
|
||||
v-for="plugin in plugins"
|
||||
:key="plugin.id"
|
||||
class="plugin-card"
|
||||
@click="launchPlugin(plugin)"
|
||||
>
|
||||
<h3>{{ plugin.name }}</h3>
|
||||
<p>{{ plugin.description || '无描述' }}</p>
|
||||
<div class="plugin-meta">
|
||||
<span class="version">版本: {{ plugin.version || '1.0.0' }}</span>
|
||||
<span class="date">{{ plugin.uploadDate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 设置界面 -->
|
||||
<div v-if="currentView === 'settings'" class="settings-section">
|
||||
<h2>系统设置</h2>
|
||||
<div class="form-group">
|
||||
<label for="theme">主题:</label>
|
||||
<select id="theme" v-model="settings.theme">
|
||||
<option value="light">明亮</option>
|
||||
<option value="dark">暗黑</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="settings.autoUpdate"
|
||||
> 自动检查更新
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button @click="saveSettings">保存设置</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { QWebChannel } from 'qwebchannel';
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
setup() {
|
||||
const currentView = ref('upload');
|
||||
const pluginForm = ref({
|
||||
name: '',
|
||||
description: ''
|
||||
});
|
||||
const selectedFile = ref(null);
|
||||
const uploading = ref(false);
|
||||
const uploadMessage = ref(null);
|
||||
const plugins = ref([]);
|
||||
const loading = ref(false);
|
||||
const settings = ref({
|
||||
theme: 'light',
|
||||
autoUpdate: true
|
||||
});
|
||||
|
||||
let py = null;
|
||||
|
||||
// 初始化QWebChannel
|
||||
onMounted(() => {
|
||||
if (window.qt && window.qt.webChannelTransport) {
|
||||
new QWebChannel(window.qt.webChannelTransport, function (channel) {
|
||||
py = channel.objects.py;
|
||||
|
||||
// 通知Python端前端已经准备就绪
|
||||
setTimeout(() => {
|
||||
if (py && py.on_frontend_ready) {
|
||||
py.on_frontend_ready();
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const handleFileUpload = (event) => {
|
||||
selectedFile.value = event.target.files[0];
|
||||
};
|
||||
|
||||
const uploadPlugin = async () => {
|
||||
if (!pluginForm.value.name || !selectedFile.value) {
|
||||
showMessage('请填写插件名称并选择文件', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
uploading.value = true;
|
||||
|
||||
try {
|
||||
if (py && py.upload_plugin) {
|
||||
// 读取文件内容并转换为base64
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
const fileContent = e.target.result.split(',')[1]; // 去掉data:application/zip;base64,前缀
|
||||
// 调用Python端的上传函数,传递文件名、描述和文件内容
|
||||
py.upload_plugin(
|
||||
pluginForm.value.name,
|
||||
pluginForm.value.description,
|
||||
fileContent,
|
||||
(result) => {
|
||||
try {
|
||||
// 检查result是否为空或无效
|
||||
if (!result) {
|
||||
showMessage('上传失败: 服务器返回空结果', 'error');
|
||||
uploading.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 解析从Python返回的JSON字符串
|
||||
const parsedResult = JSON.parse(result);
|
||||
if (parsedResult.success) {
|
||||
showMessage('插件上传成功', 'success');
|
||||
pluginForm.value = { name: '', description: '' };
|
||||
// 重置文件输入
|
||||
document.getElementById('pluginFile').value = '';
|
||||
selectedFile.value = null;
|
||||
} else {
|
||||
console.error(parsedResult);
|
||||
showMessage(`上传失败: ${parsedResult.error}`, 'error');
|
||||
}
|
||||
} catch (parseError) {
|
||||
console.error('解析Python返回结果失败:', parseError);
|
||||
console.error('原始返回结果:', result);
|
||||
showMessage(`上传失败: 返回结果解析错误`, 'error');
|
||||
}
|
||||
uploading.value = false;
|
||||
}
|
||||
);
|
||||
};
|
||||
reader.onerror = function() {
|
||||
showMessage('文件读取失败', 'error');
|
||||
uploading.value = false;
|
||||
};
|
||||
reader.readAsDataURL(selectedFile.value);
|
||||
} else {
|
||||
// 模拟上传成功
|
||||
showMessage('插件上传成功', 'success');
|
||||
pluginForm.value = { name: '', description: '' };
|
||||
// 重置文件输入
|
||||
document.getElementById('pluginFile').value = '';
|
||||
selectedFile.value = null;
|
||||
uploading.value = false;
|
||||
}
|
||||
} catch (error) {
|
||||
showMessage(`上传失败: ${error.message}`, 'error');
|
||||
uploading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const showMessage = (text, type) => {
|
||||
uploadMessage.value = { text, type };
|
||||
setTimeout(() => {
|
||||
uploadMessage.value = null;
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
const showUploadForm = () => {
|
||||
currentView.value = 'upload';
|
||||
};
|
||||
|
||||
const listPlugins = () => {
|
||||
currentView.value = 'list';
|
||||
loadPlugins();
|
||||
};
|
||||
|
||||
const showSettings = () => {
|
||||
currentView.value = 'settings';
|
||||
};
|
||||
|
||||
const loadPlugins = async () => {
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
if (py && py.list_plugins) {
|
||||
py.list_plugins((result) => {
|
||||
try {
|
||||
// 检查result是否为空或无效
|
||||
if (!result) {
|
||||
showMessage('加载插件列表失败: 服务器返回空结果', 'error');
|
||||
loading.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 解析从Python返回的JSON字符串
|
||||
const parsedResult = JSON.parse(result);
|
||||
if (parsedResult.success) {
|
||||
plugins.value = parsedResult.plugins;
|
||||
} else {
|
||||
showMessage(`加载插件列表失败: ${parsedResult.error}`, 'error');
|
||||
}
|
||||
} catch (parseError) {
|
||||
console.error('解析Python返回结果失败:', parseError);
|
||||
console.error('原始返回结果:', result);
|
||||
showMessage(`加载插件列表失败: 返回结果解析错误`, 'error');
|
||||
}
|
||||
loading.value = false;
|
||||
});
|
||||
} else {
|
||||
// 模拟插件数据
|
||||
plugins.value = [
|
||||
{ id: 1, name: '示例插件1', description: '这是一个示例插件', version: '1.0.0', uploadDate: '2023-01-01' },
|
||||
{ id: 2, name: '示例插件2', description: '另一个示例插件', version: '2.1.0', uploadDate: '2023-02-15' }
|
||||
];
|
||||
loading.value = false;
|
||||
}
|
||||
} catch (error) {
|
||||
showMessage(`加载插件列表失败: ${error.message}`, 'error');
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const launchPlugin = (plugin) => {
|
||||
if (py && py.launch_plugin) {
|
||||
py.launch_plugin(plugin.id, (result) => {
|
||||
try {
|
||||
// 检查result是否为空或无效
|
||||
if (!result) {
|
||||
showMessage('启动插件失败: 服务器返回空结果', 'error');
|
||||
return;
|
||||
}
|
||||
// 解析从Python返回的JSON字符串
|
||||
const parsedResult = JSON.parse(result);
|
||||
if (parsedResult.success) {
|
||||
console.log(parsedResult.url)
|
||||
} else {
|
||||
showMessage(`启动插件失败: ${parsedResult.error}`, 'error');
|
||||
}
|
||||
} catch (parseError) {
|
||||
console.error('解析Python返回结果失败:', parseError);
|
||||
console.error('原始返回结果:', result);
|
||||
showMessage(`启动插件失败: 返回结果解析错误`, 'error');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 模拟启动插件
|
||||
console.log(`启动插件: ${plugin.name}`);
|
||||
}
|
||||
};
|
||||
|
||||
const saveSettings = () => {
|
||||
if (py && py.save_settings) {
|
||||
// 将settings对象转换为JSON字符串再传递给Python
|
||||
const settingsJson = JSON.stringify(settings.value);
|
||||
py.save_settings(settingsJson, (result) => {
|
||||
try {
|
||||
// 检查result是否为空或无效
|
||||
if (!result) {
|
||||
showMessage('保存设置失败: 服务器返回空结果', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// 解析从Python返回的JSON字符串
|
||||
const parsedResult = JSON.parse(result);
|
||||
if (parsedResult.success) {
|
||||
showMessage('设置已保存', 'success');
|
||||
} else {
|
||||
showMessage(`保存设置失败: ${parsedResult.error}`, 'error');
|
||||
}
|
||||
} catch (parseError) {
|
||||
console.error('解析Python返回结果失败:', parseError);
|
||||
console.error('原始返回结果:', result);
|
||||
showMessage(`保存设置失败: 返回结果解析错误`, 'error');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
showMessage('设置已保存', 'success');
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
currentView,
|
||||
pluginForm,
|
||||
uploading,
|
||||
uploadMessage,
|
||||
plugins,
|
||||
loading,
|
||||
settings,
|
||||
handleFileUpload,
|
||||
uploadPlugin,
|
||||
showUploadForm,
|
||||
listPlugins,
|
||||
showSettings,
|
||||
launchPlugin,
|
||||
saveSettings
|
||||
};
|
||||
}
|
||||
};
|
||||
</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: 20px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 自定义滚动条样式 */
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #c1c1c1;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #a8a8a8;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
background: #f1f1f1;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 30px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.navbar button {
|
||||
padding: 10px 20px;
|
||||
background-color: #f0f0f0;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.navbar button:hover {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
.navbar button.active {
|
||||
background-color: #42b983;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.content {
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.form-group input,
|
||||
.form-group textarea,
|
||||
.form-group select {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
font-size: 16px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-group textarea {
|
||||
min-height: 100px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 12px 24px;
|
||||
background-color: #42b983;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
button:hover:not(:disabled) {
|
||||
background-color: #359c6d;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
background-color: #cccccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.message {
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.message.success {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
|
||||
.message.error {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
|
||||
.loading, .no-plugins {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.plugins-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.plugin-card {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.plugin-card:hover {
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.plugin-card h3 {
|
||||
margin-top: 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.plugin-card p {
|
||||
color: #666;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.plugin-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.plugin-window {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0,0,0,0.8);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.plugin-window-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background-color: #42b983;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
}
|
||||
|
||||
.plugin-window-header button {
|
||||
background: none;
|
||||
border: none;
|
||||
color: white;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.plugin-window-content {
|
||||
height: calc(100% - 50px);
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.plugins-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.navbar button {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
@@ -1,166 +0,0 @@
|
||||
<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>
|
||||
@@ -1,103 +0,0 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user