Todo: 修改因exe_token错误导致的不断重连问题
Todo: 修改打包中.bat打包文件与测试打包脚本不一致问题 New: 新增installer安装包环境搭建数据
This commit is contained in:
@@ -29,6 +29,7 @@ class BackendClient:
|
|||||||
self.error_callback: Optional[Callable] = None
|
self.error_callback: Optional[Callable] = None
|
||||||
self.login_callback: Optional[Callable] = None # 新增:平台登录(下发cookies)回调
|
self.login_callback: Optional[Callable] = None # 新增:平台登录(下发cookies)回调
|
||||||
self.success_callback: Optional[Callable] = None # 新增:后端连接成功回调
|
self.success_callback: Optional[Callable] = None # 新增:后端连接成功回调
|
||||||
|
self.token_error_callback: Optional[Callable] = None # 新增:token错误回调
|
||||||
|
|
||||||
self.is_connected = False
|
self.is_connected = False
|
||||||
|
|
||||||
@@ -261,7 +262,8 @@ class BackendClient:
|
|||||||
close: Callable = None,
|
close: Callable = None,
|
||||||
error: Callable = None,
|
error: Callable = None,
|
||||||
login: Callable = None,
|
login: Callable = None,
|
||||||
success: Callable = None):
|
success: Callable = None,
|
||||||
|
token_error: Callable = None):
|
||||||
"""设置各种消息类型的回调函数"""
|
"""设置各种消息类型的回调函数"""
|
||||||
if store_list:
|
if store_list:
|
||||||
self.store_list_callback = store_list
|
self.store_list_callback = store_list
|
||||||
@@ -279,6 +281,8 @@ class BackendClient:
|
|||||||
self.login_callback = login
|
self.login_callback = login
|
||||||
if success:
|
if success:
|
||||||
self.success_callback = success
|
self.success_callback = success
|
||||||
|
if token_error:
|
||||||
|
self.token_error_callback = token_error
|
||||||
|
|
||||||
def on_connected(self):
|
def on_connected(self):
|
||||||
"""连接成功时的处理"""
|
"""连接成功时的处理"""
|
||||||
@@ -338,7 +342,7 @@ class BackendClient:
|
|||||||
active_store_id = None
|
active_store_id = None
|
||||||
try:
|
try:
|
||||||
from Utils.JD.JdUtils import WebsocketManager as JdManager
|
from Utils.JD.JdUtils import WebsocketManager as JdManager
|
||||||
from Utils.Dy.DyUtils import WebsocketManager as DyManager
|
from Utils.Dy.DyUtils import DouYinWebsocketManager as DyManager
|
||||||
from Utils.Pdd.PddUtils import WebsocketManager as PddManager
|
from Utils.Pdd.PddUtils import WebsocketManager as PddManager
|
||||||
|
|
||||||
# 检查各平台是否有活跃连接
|
# 检查各平台是否有活跃连接
|
||||||
@@ -429,6 +433,8 @@ class BackendClient:
|
|||||||
self._handle_login(message)
|
self._handle_login(message)
|
||||||
elif msg_type == 'error':
|
elif msg_type == 'error':
|
||||||
self._handle_error_message(message)
|
self._handle_error_message(message)
|
||||||
|
elif msg_type == 'error_token':
|
||||||
|
self._handle_token_error(message)
|
||||||
elif msg_type == 'staff_list':
|
elif msg_type == 'staff_list':
|
||||||
self._handle_staff_list(message)
|
self._handle_staff_list(message)
|
||||||
else:
|
else:
|
||||||
@@ -1163,10 +1169,35 @@ class BackendClient:
|
|||||||
def _handle_error_message(self, message: Dict[str, Any]):
|
def _handle_error_message(self, message: Dict[str, Any]):
|
||||||
"""处理错误消息"""
|
"""处理错误消息"""
|
||||||
error_msg = message.get('error', '未知错误')
|
error_msg = message.get('error', '未知错误')
|
||||||
|
content = message.get('content', '')
|
||||||
|
|
||||||
|
# 检查是否为token错误(无论type是error还是error_token)
|
||||||
|
if content == "无效的exe_token" or "无效的exe_token" in content:
|
||||||
|
print(f"[错误] 检测到token错误: {content}")
|
||||||
|
self._handle_token_error(message)
|
||||||
|
return
|
||||||
|
|
||||||
print(f"后端连接错误: {error_msg}")
|
print(f"后端连接错误: {error_msg}")
|
||||||
if self.error_callback:
|
if self.error_callback:
|
||||||
self.error_callback(error_msg, message)
|
self.error_callback(error_msg, message)
|
||||||
|
|
||||||
|
def _handle_token_error(self, message: Dict[str, Any]):
|
||||||
|
"""处理token错误消息 - 无效token时停止重连并显示错误"""
|
||||||
|
error_content = message.get('content', '无效的exe_token')
|
||||||
|
print(f"[错误] Token验证失败: {error_content}")
|
||||||
|
|
||||||
|
# 停止重连机制
|
||||||
|
self.should_stop = True
|
||||||
|
self.is_reconnecting = False
|
||||||
|
|
||||||
|
# 触发token错误回调
|
||||||
|
if self.token_error_callback:
|
||||||
|
self.token_error_callback(error_content)
|
||||||
|
|
||||||
|
# 主动关闭连接
|
||||||
|
if self.websocket:
|
||||||
|
asyncio.run_coroutine_threadsafe(self.websocket.close(), self.loop)
|
||||||
|
|
||||||
def _handle_staff_list(self, message: Dict[str, Any]):
|
def _handle_staff_list(self, message: Dict[str, Any]):
|
||||||
"""处理客服列表更新消息"""
|
"""处理客服列表更新消息"""
|
||||||
staff_list = message.get('data', {}).get('staff_list', [])
|
staff_list = message.get('data', {}).get('staff_list', [])
|
||||||
|
|||||||
@@ -35,10 +35,11 @@ class WebSocketManager:
|
|||||||
'success': None,
|
'success': None,
|
||||||
'error': None,
|
'error': None,
|
||||||
'platform_connected': None,
|
'platform_connected': None,
|
||||||
|
'token_error': None,
|
||||||
}
|
}
|
||||||
|
|
||||||
def set_callbacks(self, log: Callable = None, success: Callable = None, error: Callable = None,
|
def set_callbacks(self, log: Callable = None, success: Callable = None, error: Callable = None,
|
||||||
platform_connected: Callable = None): # ← 新增参数
|
platform_connected: Callable = None, token_error: Callable = None):
|
||||||
"""设置回调函数"""
|
"""设置回调函数"""
|
||||||
if log:
|
if log:
|
||||||
self.callbacks['log'] = log
|
self.callbacks['log'] = log
|
||||||
@@ -48,6 +49,8 @@ class WebSocketManager:
|
|||||||
self.callbacks['error'] = error
|
self.callbacks['error'] = error
|
||||||
if platform_connected: # ← 新增
|
if platform_connected: # ← 新增
|
||||||
self.callbacks['platform_connected'] = platform_connected
|
self.callbacks['platform_connected'] = platform_connected
|
||||||
|
if token_error:
|
||||||
|
self.callbacks['token_error'] = token_error
|
||||||
|
|
||||||
def _log(self, message: str, level: str = "INFO"):
|
def _log(self, message: str, level: str = "INFO"):
|
||||||
"""内部日志方法"""
|
"""内部日志方法"""
|
||||||
@@ -84,6 +87,15 @@ class WebSocketManager:
|
|||||||
backend = get_backend_client()
|
backend = get_backend_client()
|
||||||
|
|
||||||
if backend:
|
if backend:
|
||||||
|
# 检查现有客户端是否因token错误而停止
|
||||||
|
if backend.should_stop:
|
||||||
|
self._log("检测到客户端因token错误已停止,创建新的客户端", "INFO")
|
||||||
|
# 断开旧客户端
|
||||||
|
backend.disconnect()
|
||||||
|
# 清除旧客户端引用
|
||||||
|
set_backend_client(None)
|
||||||
|
backend = None
|
||||||
|
else:
|
||||||
# 3 如果有客户端更新token并重连
|
# 3 如果有客户端更新token并重连
|
||||||
backend.set_token(token)
|
backend.set_token(token)
|
||||||
|
|
||||||
@@ -102,7 +114,12 @@ class WebSocketManager:
|
|||||||
"INFO")
|
"INFO")
|
||||||
self._handle_platform_login(platform_name, store_id, cookies)
|
self._handle_platform_login(platform_name, store_id, cookies)
|
||||||
|
|
||||||
backend.set_callbacks(success=_on_backend_success, login=_on_backend_login)
|
def _on_token_error(error_content: str):
|
||||||
|
self._log(f"Token验证失败: {error_content}", "ERROR")
|
||||||
|
if self.callbacks['token_error']:
|
||||||
|
self.callbacks['token_error'](error_content)
|
||||||
|
|
||||||
|
backend.set_callbacks(success=_on_backend_success, login=_on_backend_login, token_error=_on_token_error)
|
||||||
|
|
||||||
if not backend.is_connected:
|
if not backend.is_connected:
|
||||||
backend.connect()
|
backend.connect()
|
||||||
@@ -110,7 +127,9 @@ class WebSocketManager:
|
|||||||
self.backend_client = backend
|
self.backend_client = backend
|
||||||
self._log("令牌已提交,已连接后端。等待后端下发平台cookies后自动连接平台...", "SUCCESS")
|
self._log("令牌已提交,已连接后端。等待后端下发平台cookies后自动连接平台...", "SUCCESS")
|
||||||
return True
|
return True
|
||||||
else:
|
|
||||||
|
# 如果没有现有客户端或客户端被重置,创建新的
|
||||||
|
if not backend:
|
||||||
|
|
||||||
backend = BackendClient.from_exe_token(token)
|
backend = BackendClient.from_exe_token(token)
|
||||||
|
|
||||||
@@ -128,7 +147,12 @@ class WebSocketManager:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._log(f"成功回调执行失败: {e}", "ERROR")
|
self._log(f"成功回调执行失败: {e}", "ERROR")
|
||||||
|
|
||||||
backend.set_callbacks(login=_on_backend_login, success=_on_backend_success)
|
def _on_token_error(error_content: str):
|
||||||
|
self._log(f"Token验证失败: {error_content}", "ERROR")
|
||||||
|
if self.callbacks['token_error']:
|
||||||
|
self.callbacks['token_error'](error_content)
|
||||||
|
|
||||||
|
backend.set_callbacks(login=_on_backend_login, success=_on_backend_success, token_error=_on_token_error)
|
||||||
backend.connect()
|
backend.connect()
|
||||||
|
|
||||||
set_backend_client(backend)
|
set_backend_client(backend)
|
||||||
@@ -403,6 +427,11 @@ class WebSocketManager:
|
|||||||
result = asyncio.run(listener.start_with_cookies(store_id=store_id, cookies=data))
|
result = asyncio.run(listener.start_with_cookies(store_id=store_id, cookies=data))
|
||||||
self._log(f"📊 start_with_cookies 执行结果: {result}", "DEBUG")
|
self._log(f"📊 start_with_cookies 执行结果: {result}", "DEBUG")
|
||||||
|
|
||||||
|
# Cookie启动成功时也要通知GUI
|
||||||
|
if result:
|
||||||
|
self._log("✅ [PDD] Cookie启动成功,平台连接已建立", "SUCCESS")
|
||||||
|
self._notify_platform_connected("拼多多")
|
||||||
|
|
||||||
# 根据实际登录结果上报状态给后端
|
# 根据实际登录结果上报状态给后端
|
||||||
if self.backend_client and result not in ["need_verification_code", "verification_code_error", "login_failure"]:
|
if self.backend_client and result not in ["need_verification_code", "verification_code_error", "login_failure"]:
|
||||||
# 如果是特殊状态,说明通知已经在PddLogin中发送了,不需要重复发送
|
# 如果是特殊状态,说明通知已经在PddLogin中发送了,不需要重复发送
|
||||||
|
|||||||
@@ -85,9 +85,32 @@ def main():
|
|||||||
result = subprocess.run(['python', 'quick_build.py'], capture_output=False)
|
result = subprocess.run(['python', 'quick_build.py'], capture_output=False)
|
||||||
|
|
||||||
if result.returncode == 0:
|
if result.returncode == 0:
|
||||||
|
# 确保输出目录正确性
|
||||||
|
if os.path.exists('dist/main') and not os.path.exists('dist/MultiPlatformGUI'):
|
||||||
|
print("🔄 修正输出目录名称...")
|
||||||
|
try:
|
||||||
|
os.rename('dist/main', 'dist/MultiPlatformGUI')
|
||||||
|
print("✅ 已重命名为: dist/MultiPlatformGUI/")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ 重命名失败: {e}")
|
||||||
|
|
||||||
|
# 验证最终输出
|
||||||
|
if os.path.exists('dist/MultiPlatformGUI'):
|
||||||
print("\n🎉 生产环境打包成功!")
|
print("\n🎉 生产环境打包成功!")
|
||||||
print("📁 输出目录: dist/MultiPlatformGUI/")
|
print("📁 输出目录: dist/MultiPlatformGUI/")
|
||||||
print("🔇 已禁用所有日志功能,客户端不会产生日志文件")
|
print("🔇 已禁用所有日志功能,客户端不会产生日志文件")
|
||||||
|
|
||||||
|
# 验证关键文件
|
||||||
|
exe_path = 'dist/MultiPlatformGUI/main.exe'
|
||||||
|
if os.path.exists(exe_path):
|
||||||
|
size = os.path.getsize(exe_path) / 1024 / 1024 # MB
|
||||||
|
print(f"✅ 主程序: main.exe ({size:.1f} MB)")
|
||||||
|
else:
|
||||||
|
print("⚠️ 主程序文件未找到")
|
||||||
|
else:
|
||||||
|
print("\n❌ 输出目录验证失败")
|
||||||
|
if os.path.exists('dist/main'):
|
||||||
|
print("💡 发现: dist/main/ 目录,请手动重命名为 MultiPlatformGUI")
|
||||||
else:
|
else:
|
||||||
print("\n❌ 打包失败")
|
print("\n❌ 打包失败")
|
||||||
|
|
||||||
|
|||||||
106
installer/README.md
Normal file
106
installer/README.md
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
# GUI 安装包构建工具
|
||||||
|
|
||||||
|
这个目录包含了使用 NSIS 构建 Windows 安装包的完整解决方案。
|
||||||
|
|
||||||
|
## 目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
installer/
|
||||||
|
├── build_installer.py # 主要的构建脚本
|
||||||
|
├── installer.nsi # 自动生成的 NSIS 脚本
|
||||||
|
├── assets/ # 安装包资源文件
|
||||||
|
│ ├── icon.png # 应用程序图标
|
||||||
|
│ └── license.txt # 软件许可协议
|
||||||
|
└── output/ # 生成的安装包输出目录
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
### 方法一:使用批处理脚本(推荐)
|
||||||
|
```bash
|
||||||
|
# 在 GUI_main 目录下运行
|
||||||
|
build_installer.bat
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方法二:直接运行Python脚本
|
||||||
|
```bash
|
||||||
|
# 在 GUI_main/installer 目录下运行
|
||||||
|
python build_installer.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## 前提条件
|
||||||
|
|
||||||
|
1. **NSIS 安装**
|
||||||
|
- 下载地址:https://nsis.sourceforge.io/Download
|
||||||
|
- 安装后确保 `makensis` 命令在 PATH 中
|
||||||
|
|
||||||
|
2. **应用程序构建**
|
||||||
|
- 必须先运行 `build_production.py` 构建应用程序
|
||||||
|
- 确保 `dist/MultiPlatformGUI/` 目录存在
|
||||||
|
|
||||||
|
## 构建流程
|
||||||
|
|
||||||
|
1. **环境检查**:验证 NSIS 安装和应用程序构建
|
||||||
|
2. **准备资源**:复制图标文件和创建许可证
|
||||||
|
3. **生成脚本**:自动生成 NSIS 安装脚本
|
||||||
|
4. **编译安装包**:使用 makensis 编译最终的安装程序
|
||||||
|
|
||||||
|
## 输出结果
|
||||||
|
|
||||||
|
安装包将生成在 `installer/output/` 目录下,文件名格式:
|
||||||
|
```
|
||||||
|
ShuiDi_AI_Assistant_Setup_1.0.0_YYYYMMDD_HHMMSS.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
## 安装包功能
|
||||||
|
|
||||||
|
- ✅ 完整的中文界面
|
||||||
|
- ✅ 自动创建开始菜单快捷方式
|
||||||
|
- ✅ 自动创建桌面快捷方式
|
||||||
|
- ✅ 注册表集成
|
||||||
|
- ✅ 完整的卸载功能
|
||||||
|
- ✅ 版本信息显示
|
||||||
|
|
||||||
|
## 自定义配置
|
||||||
|
|
||||||
|
可以修改 `build_installer.py` 中的以下配置:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 应用程序信息
|
||||||
|
self.app_name = "水滴AI客服智能助手"
|
||||||
|
self.app_name_en = "ShuiDi AI Assistant"
|
||||||
|
self.app_version = "1.0.0"
|
||||||
|
self.app_publisher = "水滴科技"
|
||||||
|
self.app_url = "https://www.shuidi.tech"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 故障排除
|
||||||
|
|
||||||
|
### 常见问题
|
||||||
|
|
||||||
|
1. **找不到 makensis 命令**
|
||||||
|
- 确保 NSIS 已正确安装
|
||||||
|
- 将 NSIS 安装目录添加到 PATH 环境变量
|
||||||
|
|
||||||
|
2. **找不到 dist 目录**
|
||||||
|
- 先运行 `build_production.py` 构建应用程序
|
||||||
|
|
||||||
|
3. **图标文件问题**
|
||||||
|
- 确保 `static/` 目录下有图标文件
|
||||||
|
- 建议准备 ICO 格式的图标文件
|
||||||
|
|
||||||
|
### 日志和调试
|
||||||
|
|
||||||
|
构建过程中的详细信息会在控制台显示,包括:
|
||||||
|
- NSIS 版本信息
|
||||||
|
- 文件复制状态
|
||||||
|
- 编译过程输出
|
||||||
|
- 最终安装包信息
|
||||||
|
|
||||||
|
## 进一步优化
|
||||||
|
|
||||||
|
可以考虑添加以下功能:
|
||||||
|
- 数字签名支持
|
||||||
|
- 多语言支持
|
||||||
|
- 自定义安装选项
|
||||||
|
- 更新检查机制
|
||||||
325
installer/build_installer.py
Normal file
325
installer/build_installer.py
Normal file
@@ -0,0 +1,325 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
NSIS 安装包构建脚本
|
||||||
|
用于创建 GUI 应用程序的Windows安装包
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
class NSISInstaller:
|
||||||
|
def __init__(self):
|
||||||
|
self.script_dir = Path(__file__).parent
|
||||||
|
self.project_root = self.script_dir.parent
|
||||||
|
self.dist_dir = self.project_root / "dist" / "MultiPlatformGUI"
|
||||||
|
self.output_dir = self.script_dir / "output"
|
||||||
|
self.assets_dir = self.script_dir / "assets"
|
||||||
|
self.nsis_script = self.script_dir / "installer.nsi"
|
||||||
|
|
||||||
|
# 应用程序信息
|
||||||
|
self.app_name = "水滴AI客服智能助手"
|
||||||
|
self.app_name_en = "ShuiDi AI Assistant"
|
||||||
|
self.app_version = "1.0.0"
|
||||||
|
self.app_publisher = "水滴智能科技"
|
||||||
|
self.app_url = "https://shuidrop.com/"
|
||||||
|
self.exe_name = "main.exe"
|
||||||
|
|
||||||
|
def check_prerequisites(self):
|
||||||
|
"""检查构建前提条件"""
|
||||||
|
print("🔍 检查构建前提条件...")
|
||||||
|
|
||||||
|
# 检查NSIS安装
|
||||||
|
try:
|
||||||
|
result = subprocess.run(['makensis', '/VERSION'],
|
||||||
|
capture_output=True, text=True, check=True)
|
||||||
|
nsis_version = result.stdout.strip()
|
||||||
|
print(f"✅ NSIS 版本: {nsis_version}")
|
||||||
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||||
|
print("❌ 错误: 未找到NSIS或makensis命令")
|
||||||
|
print(" 请从 https://nsis.sourceforge.io/Download 下载并安装NSIS")
|
||||||
|
print(" 确保将NSIS添加到系统PATH环境变量")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 检查dist目录
|
||||||
|
if not self.dist_dir.exists():
|
||||||
|
print(f"❌ 错误: 未找到构建输出目录 {self.dist_dir}")
|
||||||
|
print(" 请先运行 build_production.py 构建应用程序")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 检查主程序文件
|
||||||
|
exe_path = self.dist_dir / self.exe_name
|
||||||
|
if not exe_path.exists():
|
||||||
|
print(f"❌ 错误: 未找到主程序文件 {exe_path}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
print(f"✅ 找到主程序: {exe_path}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def prepare_assets(self):
|
||||||
|
"""准备安装包资源文件"""
|
||||||
|
print("📁 准备资源文件...")
|
||||||
|
|
||||||
|
# 创建assets目录
|
||||||
|
self.assets_dir.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
# 复制图标文件
|
||||||
|
icon_sources = [
|
||||||
|
self.project_root / "static" / "ai_assistant_icon_64.png",
|
||||||
|
self.project_root / "static" / "ai_assistant_icon_32.png",
|
||||||
|
self.project_root / "static" / "ai_assistant_icon_16.png"
|
||||||
|
]
|
||||||
|
|
||||||
|
# 寻找可用的图标文件
|
||||||
|
icon_found = False
|
||||||
|
for icon_source in icon_sources:
|
||||||
|
if icon_source.exists():
|
||||||
|
# 如果有PNG图标,我们需要转换为ICO格式
|
||||||
|
# 这里暂时复制PNG,实际项目中建议准备ICO格式图标
|
||||||
|
icon_dest = self.assets_dir / "icon.ico"
|
||||||
|
try:
|
||||||
|
# 尝试使用PIL转换PNG为ICO格式
|
||||||
|
from PIL import Image
|
||||||
|
img = Image.open(icon_source)
|
||||||
|
ico_path = self.assets_dir / "icon.ico"
|
||||||
|
img.save(ico_path, format='ICO', sizes=[(16,16), (32,32), (48,48), (64,64)])
|
||||||
|
print(f"✅ 转换图标: {icon_source.name} -> icon.ico")
|
||||||
|
icon_found = True
|
||||||
|
break
|
||||||
|
except ImportError:
|
||||||
|
print("⚠️ 未安装PIL库,跳过图标转换")
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ 图标转换失败: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not icon_found:
|
||||||
|
print("⚠️ 未找到图标文件,将使用默认图标")
|
||||||
|
|
||||||
|
return icon_found
|
||||||
|
|
||||||
|
def generate_nsis_script(self, has_icon=False):
|
||||||
|
"""生成NSIS安装脚本"""
|
||||||
|
print("📝 生成NSIS安装脚本...")
|
||||||
|
|
||||||
|
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
installer_name = f"{self.app_name_en}_Setup_{self.app_version}_{timestamp}.exe"
|
||||||
|
|
||||||
|
nsis_content = f'''# 水滴AI客服智能助手 NSIS 安装脚本
|
||||||
|
# 自动生成于 {datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
|
||||||
|
|
||||||
|
Unicode True
|
||||||
|
|
||||||
|
# 定义应用程序信息
|
||||||
|
!define APP_NAME "{self.app_name}"
|
||||||
|
!define APP_NAME_EN "{self.app_name_en}"
|
||||||
|
!define APP_VERSION "{self.app_version}"
|
||||||
|
!define APP_PUBLISHER "{self.app_publisher}"
|
||||||
|
!define APP_URL "{self.app_url}"
|
||||||
|
!define APP_EXE "{self.exe_name}"
|
||||||
|
|
||||||
|
# 安装程序信息
|
||||||
|
Name "${{APP_NAME}} v${{APP_VERSION}}"
|
||||||
|
OutFile "output\\{installer_name}"
|
||||||
|
InstallDir "$PROGRAMFILES\\${{APP_NAME_EN}}"
|
||||||
|
InstallDirRegKey HKLM "Software\\${{APP_PUBLISHER}}\\${{APP_NAME_EN}}" "InstallPath"
|
||||||
|
|
||||||
|
# 界面设置
|
||||||
|
!include "MUI2.nsh"
|
||||||
|
!define MUI_ABORTWARNING
|
||||||
|
{f'!define MUI_ICON "assets\\\\icon.ico"' if has_icon else ''}
|
||||||
|
{f'!define MUI_UNICON "assets\\\\icon.ico"' if has_icon else ''}
|
||||||
|
|
||||||
|
# 页面配置
|
||||||
|
!insertmacro MUI_PAGE_WELCOME
|
||||||
|
!insertmacro MUI_PAGE_LICENSE "assets\\license.txt"
|
||||||
|
!insertmacro MUI_PAGE_DIRECTORY
|
||||||
|
!insertmacro MUI_PAGE_INSTFILES
|
||||||
|
!insertmacro MUI_PAGE_FINISH
|
||||||
|
|
||||||
|
!insertmacro MUI_UNPAGE_WELCOME
|
||||||
|
!insertmacro MUI_UNPAGE_CONFIRM
|
||||||
|
!insertmacro MUI_UNPAGE_INSTFILES
|
||||||
|
!insertmacro MUI_UNPAGE_FINISH
|
||||||
|
|
||||||
|
# 语言
|
||||||
|
!insertmacro MUI_LANGUAGE "SimpChinese"
|
||||||
|
|
||||||
|
# 版本信息
|
||||||
|
VIProductVersion "{self.app_version}.0"
|
||||||
|
VIAddVersionKey /LANG=2052 "ProductName" "${{APP_NAME}}"
|
||||||
|
VIAddVersionKey /LANG=2052 "Comments" "水滴AI客服智能助手"
|
||||||
|
VIAddVersionKey /LANG=2052 "CompanyName" "${{APP_PUBLISHER}}"
|
||||||
|
VIAddVersionKey /LANG=2052 "FileDescription" "${{APP_NAME}} 安装程序"
|
||||||
|
VIAddVersionKey /LANG=2052 "FileVersion" "${{APP_VERSION}}"
|
||||||
|
VIAddVersionKey /LANG=2052 "ProductVersion" "${{APP_VERSION}}"
|
||||||
|
VIAddVersionKey /LANG=2052 "OriginalFilename" "{installer_name}"
|
||||||
|
VIAddVersionKey /LANG=2052 "LegalCopyright" "© 2024 ${{APP_PUBLISHER}}"
|
||||||
|
|
||||||
|
# 安装段
|
||||||
|
Section "MainSection" SEC01
|
||||||
|
SetOutPath "$INSTDIR"
|
||||||
|
SetOverwrite on
|
||||||
|
|
||||||
|
# 复制所有文件
|
||||||
|
File /r "..\\dist\\MultiPlatformGUI\\*.*"
|
||||||
|
|
||||||
|
# 复制图标文件到安装目录(如果有的话)
|
||||||
|
{f'File "assets\\\\icon.ico"' if has_icon else ''}
|
||||||
|
|
||||||
|
# 创建开始菜单快捷方式
|
||||||
|
CreateDirectory "$SMPROGRAMS\\${{APP_NAME}}"
|
||||||
|
{f'CreateShortCut "$SMPROGRAMS\\\\${{APP_NAME}}\\\\${{APP_NAME}}.lnk" "$INSTDIR\\\\${{APP_EXE}}" "" "$INSTDIR\\\\icon.ico" 0' if has_icon else 'CreateShortCut "$SMPROGRAMS\\\\${{APP_NAME}}\\\\${{APP_NAME}}.lnk" "$INSTDIR\\\\${{APP_EXE}}"'}
|
||||||
|
{f'CreateShortCut "$SMPROGRAMS\\\\${{APP_NAME}}\\\\卸载${{APP_NAME}}.lnk" "$INSTDIR\\\\uninstall.exe" "" "$INSTDIR\\\\icon.ico" 0' if has_icon else 'CreateShortCut "$SMPROGRAMS\\\\${{APP_NAME}}\\\\卸载${{APP_NAME}}.lnk" "$INSTDIR\\\\uninstall.exe"'}
|
||||||
|
|
||||||
|
# 创建桌面快捷方式
|
||||||
|
{f'CreateShortCut "$DESKTOP\\\\${{APP_NAME}}.lnk" "$INSTDIR\\\\${{APP_EXE}}" "" "$INSTDIR\\\\icon.ico" 0' if has_icon else 'CreateShortCut "$DESKTOP\\\\${{APP_NAME}}.lnk" "$INSTDIR\\\\${{APP_EXE}}"'}
|
||||||
|
|
||||||
|
# 写入注册表
|
||||||
|
WriteRegStr HKLM "Software\\${{APP_PUBLISHER}}\\${{APP_NAME_EN}}" "InstallPath" "$INSTDIR"
|
||||||
|
WriteRegStr HKLM "Software\\${{APP_PUBLISHER}}\\${{APP_NAME_EN}}" "Version" "${{APP_VERSION}}"
|
||||||
|
|
||||||
|
# 写入卸载信息
|
||||||
|
WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${{APP_NAME_EN}}" "DisplayName" "${{APP_NAME}}"
|
||||||
|
WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${{APP_NAME_EN}}" "UninstallString" "$INSTDIR\\uninstall.exe"
|
||||||
|
WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${{APP_NAME_EN}}" "DisplayVersion" "${{APP_VERSION}}"
|
||||||
|
WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${{APP_NAME_EN}}" "Publisher" "${{APP_PUBLISHER}}"
|
||||||
|
WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${{APP_NAME_EN}}" "URLInfoAbout" "${{APP_URL}}"
|
||||||
|
WriteRegDWORD HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${{APP_NAME_EN}}" "NoModify" 1
|
||||||
|
WriteRegDWORD HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${{APP_NAME_EN}}" "NoRepair" 1
|
||||||
|
|
||||||
|
# 创建卸载程序
|
||||||
|
WriteUninstaller "$INSTDIR\\uninstall.exe"
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
# 卸载段
|
||||||
|
Section "Uninstall"
|
||||||
|
# 删除快捷方式
|
||||||
|
Delete "$SMPROGRAMS\\${{APP_NAME}}\\${{APP_NAME}}.lnk"
|
||||||
|
Delete "$SMPROGRAMS\\${{APP_NAME}}\\卸载${{APP_NAME}}.lnk"
|
||||||
|
RMDir "$SMPROGRAMS\\${{APP_NAME}}"
|
||||||
|
Delete "$DESKTOP\\${{APP_NAME}}.lnk"
|
||||||
|
|
||||||
|
# 删除安装目录
|
||||||
|
RMDir /r "$INSTDIR"
|
||||||
|
|
||||||
|
# 删除注册表项
|
||||||
|
DeleteRegKey HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${{APP_NAME_EN}}"
|
||||||
|
DeleteRegKey HKLM "Software\\${{APP_PUBLISHER}}\\${{APP_NAME_EN}}"
|
||||||
|
SectionEnd
|
||||||
|
'''
|
||||||
|
|
||||||
|
# 写入NSIS脚本文件
|
||||||
|
# 使用UTF-8 BOM编码,NSIS可以正确识别
|
||||||
|
with open(self.nsis_script, 'w', encoding='utf-8-sig') as f:
|
||||||
|
f.write(nsis_content)
|
||||||
|
|
||||||
|
print(f"✅ NSIS脚本已生成: {self.nsis_script}")
|
||||||
|
return installer_name
|
||||||
|
|
||||||
|
def create_license_file(self):
|
||||||
|
"""创建许可证文件"""
|
||||||
|
license_file = self.assets_dir / "license.txt"
|
||||||
|
license_content = f"""软件许可协议
|
||||||
|
|
||||||
|
{self.app_name} v{self.app_version}
|
||||||
|
|
||||||
|
版权所有 © 2025 {self.app_publisher}
|
||||||
|
|
||||||
|
使用本软件即表示您同意以下条款:
|
||||||
|
|
||||||
|
1. 本软件仅供合法用途使用
|
||||||
|
2. 不得对软件进行反向工程、反编译或反汇编
|
||||||
|
3. 软件按"原样"提供,不提供任何明示或暗示的保证
|
||||||
|
4. 在任何情况下,软件提供者均不对因使用本软件而产生的任何损害承担责任
|
||||||
|
|
||||||
|
如有疑问,请联系:{self.app_url}
|
||||||
|
"""
|
||||||
|
|
||||||
|
with open(license_file, 'w', encoding='utf-8-sig') as f:
|
||||||
|
f.write(license_content)
|
||||||
|
|
||||||
|
print(f"✅ 许可证文件已创建: {license_file}")
|
||||||
|
|
||||||
|
def build_installer(self):
|
||||||
|
"""构建安装包"""
|
||||||
|
print("🚀 开始构建NSIS安装包...")
|
||||||
|
|
||||||
|
# 创建输出目录
|
||||||
|
self.output_dir.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
# 执行NSIS编译
|
||||||
|
try:
|
||||||
|
cmd = ['makensis', str(self.nsis_script)]
|
||||||
|
result = subprocess.run(cmd, cwd=str(self.script_dir),
|
||||||
|
capture_output=True, text=True, check=True)
|
||||||
|
|
||||||
|
print("✅ NSIS编译成功")
|
||||||
|
if result.stdout:
|
||||||
|
print("NSIS输出:")
|
||||||
|
print(result.stdout)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print("❌ NSIS编译失败")
|
||||||
|
print(f"错误信息: {e.stderr}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""执行完整的构建流程"""
|
||||||
|
print("=" * 60)
|
||||||
|
print(f"🔧 {self.app_name} 安装包构建工具")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 1. 检查前提条件
|
||||||
|
if not self.check_prerequisites():
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 2. 准备资源文件
|
||||||
|
has_icon = self.prepare_assets()
|
||||||
|
|
||||||
|
# 3. 创建许可证文件
|
||||||
|
self.create_license_file()
|
||||||
|
|
||||||
|
# 4. 生成NSIS脚本
|
||||||
|
installer_name = self.generate_nsis_script(has_icon)
|
||||||
|
|
||||||
|
# 5. 构建安装包
|
||||||
|
if not self.build_installer():
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 6. 显示结果
|
||||||
|
installer_path = self.output_dir / installer_name
|
||||||
|
if installer_path.exists():
|
||||||
|
installer_size = installer_path.stat().st_size / (1024 * 1024)
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("🎉 安装包构建成功!")
|
||||||
|
print(f"📁 安装包位置: {installer_path}")
|
||||||
|
print(f"📏 文件大小: {installer_size:.1f} MB")
|
||||||
|
print("🚀 可以直接分发给用户使用")
|
||||||
|
print("=" * 60)
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print("❌ 安装包文件未找到")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 构建过程出错: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主函数"""
|
||||||
|
installer = NSISInstaller()
|
||||||
|
success = installer.run()
|
||||||
|
|
||||||
|
if not success:
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
26
main.py
26
main.py
@@ -402,6 +402,29 @@ class LoginWindow(QMainWindow):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.add_log(f"处理平台连接事件失败: {e}", "ERROR")
|
self.add_log(f"处理平台连接事件失败: {e}", "ERROR")
|
||||||
|
|
||||||
|
def on_token_error(self, error_content: str):
|
||||||
|
"""处理token错误 - 显示红色错误信息并停止所有操作"""
|
||||||
|
try:
|
||||||
|
self.add_log(f"Token验证失败: {error_content}", "ERROR")
|
||||||
|
|
||||||
|
# 在状态标签显示红色错误信息
|
||||||
|
self.status_label.setText(f"🔴 {error_content}")
|
||||||
|
self.status_label.setStyleSheet(
|
||||||
|
"color: #dc3545; background: rgba(220, 53, 69, 0.1); border-radius: 12px; padding: 5px 10px; font-weight: bold;")
|
||||||
|
|
||||||
|
# 重置按钮状态
|
||||||
|
self.login_btn.setEnabled(True)
|
||||||
|
self.login_btn.setText("重新连接")
|
||||||
|
self.login_btn.setObjectName("loginButton") # 恢复原始样式
|
||||||
|
|
||||||
|
# 清空已连接平台列表
|
||||||
|
self.connected_platforms.clear()
|
||||||
|
|
||||||
|
self.add_log("由于token无效,已停止所有连接操作", "ERROR")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.add_log(f"处理token错误失败: {e}", "ERROR")
|
||||||
|
|
||||||
def delayed_platform_summary(self):
|
def delayed_platform_summary(self):
|
||||||
"""定时器触发的汇总显示更新"""
|
"""定时器触发的汇总显示更新"""
|
||||||
try:
|
try:
|
||||||
@@ -615,7 +638,8 @@ class LoginWindow(QMainWindow):
|
|||||||
log=self.add_log,
|
log=self.add_log,
|
||||||
success=lambda: self.add_log("WebSocket连接管理器连接成功", "SUCCESS"),
|
success=lambda: self.add_log("WebSocket连接管理器连接成功", "SUCCESS"),
|
||||||
error=lambda error: self.add_log(f"WebSocket连接管理器错误: {error}", "ERROR"),
|
error=lambda error: self.add_log(f"WebSocket连接管理器错误: {error}", "ERROR"),
|
||||||
platform_connected=self.on_platform_connected # 新增:平台连接回调
|
platform_connected=self.on_platform_connected, # 新增:平台连接回调
|
||||||
|
token_error=self.on_token_error # 新增:token错误回调
|
||||||
)
|
)
|
||||||
|
|
||||||
# 连接后端
|
# 连接后端
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import shutil
|
import shutil
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
def clean_build():
|
def clean_build():
|
||||||
@@ -96,17 +97,31 @@ def build_with_command():
|
|||||||
print("✅ 打包成功!")
|
print("✅ 打包成功!")
|
||||||
print("📁 打包结果: dist/main/")
|
print("📁 打包结果: dist/main/")
|
||||||
|
|
||||||
# 重命名目录
|
# 重命名目录为统一的输出路径
|
||||||
if os.path.exists('dist/main'):
|
if os.path.exists('dist/main'):
|
||||||
try:
|
try:
|
||||||
|
# 如果目标目录已存在,先删除
|
||||||
if os.path.exists('dist/MultiPlatformGUI'):
|
if os.path.exists('dist/MultiPlatformGUI'):
|
||||||
|
print("🗑️ 删除旧的 MultiPlatformGUI 目录...")
|
||||||
shutil.rmtree('dist/MultiPlatformGUI')
|
shutil.rmtree('dist/MultiPlatformGUI')
|
||||||
|
|
||||||
|
# 重命名
|
||||||
os.rename('dist/main', 'dist/MultiPlatformGUI')
|
os.rename('dist/main', 'dist/MultiPlatformGUI')
|
||||||
print("✅ 已重命名为: dist/MultiPlatformGUI/")
|
print("✅ 已重命名为: dist/MultiPlatformGUI/")
|
||||||
|
|
||||||
|
# 验证重命名结果
|
||||||
|
if os.path.exists('dist/MultiPlatformGUI/main.exe'):
|
||||||
|
exe_size = os.path.getsize('dist/MultiPlatformGUI/main.exe') / 1024 / 1024
|
||||||
|
print(f"✅ 主程序验证通过: main.exe ({exe_size:.1f} MB)")
|
||||||
|
else:
|
||||||
|
print("⚠️ 重命名后主程序文件验证失败")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"⚠️ 重命名失败: {e}")
|
print(f"⚠️ 重命名失败: {e}")
|
||||||
print("💡 可以手动重命名: dist/main -> dist/MultiPlatformGUI")
|
print("💡 可以手动重命名: dist/main -> dist/MultiPlatformGUI")
|
||||||
print("📁 当前可用路径: dist/main/")
|
print("📁 当前可用路径: dist/main/")
|
||||||
|
else:
|
||||||
|
print("⚠️ 未找到 dist/main 目录,重命名跳过")
|
||||||
|
|
||||||
# 创建使用说明
|
# 创建使用说明
|
||||||
try:
|
try:
|
||||||
@@ -221,32 +236,63 @@ def verify_result():
|
|||||||
"""验证打包结果"""
|
"""验证打包结果"""
|
||||||
print("\n🔍 验证打包结果...")
|
print("\n🔍 验证打包结果...")
|
||||||
|
|
||||||
# 先检查MultiPlatformGUI,再检查main目录
|
# 优先检查MultiPlatformGUI(标准输出目录)
|
||||||
base_dirs = ["dist/MultiPlatformGUI", "dist/main"]
|
target_dir = "dist/MultiPlatformGUI"
|
||||||
|
fallback_dir = "dist/main"
|
||||||
|
|
||||||
for base_dir in base_dirs:
|
if os.path.exists(target_dir):
|
||||||
if os.path.exists(base_dir):
|
return _verify_directory(target_dir)
|
||||||
exe_path = f"{base_dir}/main.exe"
|
elif os.path.exists(fallback_dir):
|
||||||
dll_path = f"{base_dir}/_internal/Utils/PythonNew32/SaiNiuApi.dll"
|
print(f"⚠️ 发现备用目录: {fallback_dir}")
|
||||||
|
print("💡 建议重命名为: dist/MultiPlatformGUI")
|
||||||
if os.path.exists(exe_path):
|
return _verify_directory(fallback_dir)
|
||||||
size = os.path.getsize(exe_path)
|
|
||||||
print(f"✅ 主程序: {exe_path} ({size:,} bytes)")
|
|
||||||
|
|
||||||
if os.path.exists(dll_path):
|
|
||||||
dll_size = os.path.getsize(dll_path)
|
|
||||||
print(f"✅ 千牛DLL: SaiNiuApi.dll ({dll_size:,} bytes)")
|
|
||||||
print(f"📁 有效路径: {base_dir}/")
|
|
||||||
print("✅ 验证通过")
|
|
||||||
return True
|
|
||||||
else:
|
else:
|
||||||
print("❌ 千牛DLL不存在")
|
print("❌ 未找到任何打包输出目录")
|
||||||
else:
|
|
||||||
print(f"❌ {exe_path} 不存在")
|
|
||||||
|
|
||||||
print("❌ 未找到有效的打包结果")
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def _verify_directory(base_dir):
|
||||||
|
"""验证指定目录的打包结果"""
|
||||||
|
exe_path = f"{base_dir}/main.exe"
|
||||||
|
internal_dir = f"{base_dir}/_internal"
|
||||||
|
dll_path = f"{base_dir}/_internal/Utils/PythonNew32/SaiNiuApi.dll"
|
||||||
|
static_dir = f"{base_dir}/_internal/static"
|
||||||
|
|
||||||
|
print(f"📁 验证目录: {base_dir}")
|
||||||
|
|
||||||
|
# 检查主程序
|
||||||
|
if os.path.exists(exe_path):
|
||||||
|
size = os.path.getsize(exe_path) / 1024 / 1024 # MB
|
||||||
|
print(f"✅ 主程序: main.exe ({size:.1f} MB)")
|
||||||
|
else:
|
||||||
|
print("❌ 主程序 main.exe 不存在")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 检查_internal目录
|
||||||
|
if os.path.exists(internal_dir):
|
||||||
|
file_count = len([f for f in os.listdir(internal_dir) if os.path.isfile(os.path.join(internal_dir, f))])
|
||||||
|
dir_count = len([d for d in os.listdir(internal_dir) if os.path.isdir(os.path.join(internal_dir, d))])
|
||||||
|
print(f"✅ 依赖目录: _internal/ ({file_count} 个文件, {dir_count} 个子目录)")
|
||||||
|
else:
|
||||||
|
print("❌ _internal 目录不存在")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 检查关键DLL(可选)
|
||||||
|
if os.path.exists(dll_path):
|
||||||
|
dll_size = os.path.getsize(dll_path) / 1024 # KB
|
||||||
|
print(f"✅ 千牛DLL: SaiNiuApi.dll ({dll_size:.1f} KB)")
|
||||||
|
else:
|
||||||
|
print("⚠️ 千牛DLL不存在(可能正常)")
|
||||||
|
|
||||||
|
# 检查静态资源
|
||||||
|
if os.path.exists(static_dir):
|
||||||
|
icon_files = list(Path(static_dir).glob("*.png"))
|
||||||
|
print(f"✅ 静态资源: static/ ({len(icon_files)} 个图标文件)")
|
||||||
|
else:
|
||||||
|
print("⚠️ static 目录不存在")
|
||||||
|
|
||||||
|
print(f"✅ 目录 {base_dir} 验证通过")
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""主函数"""
|
"""主函数"""
|
||||||
@@ -296,10 +342,11 @@ def main():
|
|||||||
if os.path.exists("dist/MultiPlatformGUI"):
|
if os.path.exists("dist/MultiPlatformGUI"):
|
||||||
print("📁 打包结果: dist/MultiPlatformGUI/")
|
print("📁 打包结果: dist/MultiPlatformGUI/")
|
||||||
print("🚀 运行方式: cd dist/MultiPlatformGUI && .\\main.exe")
|
print("🚀 运行方式: cd dist/MultiPlatformGUI && .\\main.exe")
|
||||||
|
print("📦 安装包构建: python installer/build_installer.py")
|
||||||
elif os.path.exists("dist/main"):
|
elif os.path.exists("dist/main"):
|
||||||
print("<EFBFBD><EFBFBD> 打包结果: dist/main/")
|
print("📁 打包结果: dist/main/")
|
||||||
print("🚀 运行方式: cd dist/main && .\\main.exe")
|
print("🚀 运行方式: cd dist/main && .\\main.exe")
|
||||||
print("💡 提示: 可手动重命名为 MultiPlatformGUI")
|
print("⚠️ 建议重命名: dist/main -> dist/MultiPlatformGUI")
|
||||||
|
|
||||||
print("=" * 60)
|
print("=" * 60)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user