Files
orc-order-v2/build_exe.py
T
houhuan 6fd14b4e49 fix: EXE版禁用一键安装拖拽,打包时集成tkinterdnd2
- EXE版(sys.frozen)不显示安装按钮,提示用源码版安装后重新打包
- tkinterdnd2 加入 hidden_imports,打包时自动带上

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 21:26:29 +08:00

357 lines
11 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
OCR订单处理系统 - EXE打包脚本
============================
自动化打包脚本,包含所有必要的资源文件和配置
"""
import os
import sys
import shutil
import subprocess
from pathlib import Path
def clean_build():
"""清理之前的构建文件"""
print("清理构建目录...")
dirs_to_clean = ['build', 'dist', '__pycache__']
for dir_name in dirs_to_clean:
if os.path.exists(dir_name):
shutil.rmtree(dir_name)
print(f"已删除: {dir_name}")
# 删除spec文件
spec_files = [f for f in os.listdir('.') if f.endswith('.spec')]
for spec_file in spec_files:
os.remove(spec_file)
print(f"已删除: {spec_file}")
def create_spec_file():
"""创建PyInstaller spec文件"""
spec_content = '''
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
# 需要包含的数据文件
added_files = [
('config.ini', '.'),
('config/barcode_mappings.json', 'config/'),
('config/config.ini', 'config/'),
('templates/银豹-采购单模板.xls', 'templates/'),
('app', 'app'),
]
# 需要隐式导入的模块
hidden_imports = [
'tkinter',
'tkinter.ttk',
'tkinter.filedialog',
'tkinter.messagebox',
'tkinter.scrolledtext',
'pandas',
'numpy',
'openpyxl',
'xlrd',
'xlwt',
'xlutils',
'requests',
'dotenv',
'tkinterdnd2',
'configparser',
'threading',
'datetime',
'json',
're',
'subprocess',
'shutil',
'app.config.settings',
'app.services.ocr_service',
'app.services.order_service',
'app.services.tobacco_service',
'app.services.processor_service',
'app.core.utils.dialog_utils',
'app.core.utils.file_utils',
'app.core.utils.log_utils',
'app.core.utils.string_utils',
'app.core.handlers.column_mapper',
'app.core.excel.converter',
'app.core.db.product_db',
'app.ui.error_utils',
'app.ui.theme',
'app.ui.logging_ui',
'app.ui.ui_widgets',
'app.ui.user_settings',
'app.ui.result_previews',
'app.ui.command_runner',
'app.ui.file_operations',
'app.ui.action_handlers',
'app.ui.barcode_editor',
'app.ui.config_dialog',
'app.ui.shortcuts',
'app.ui.main_window',
]
a = Analysis(
['启动器.py'],
pathex=[],
binaries=[],
datas=added_files,
hiddenimports=hidden_imports,
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='OCR订单处理系统',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
'''
with open('OCR订单处理系统.spec', 'w', encoding='utf-8') as f:
f.write(spec_content)
print("已创建spec文件: OCR订单处理系统.spec")
def build_exe():
"""构建EXE文件"""
print("开始构建EXE文件...")
try:
# 注入版本信息到根config.ini
try:
root_cfg = Path('config.ini')
from datetime import datetime
version_str = datetime.now().strftime('%Y.%m.%d.%H%M')
if root_cfg.exists():
lines = root_cfg.read_text(encoding='utf-8').splitlines()
has_app = any(l.strip().lower() == '[app]' for l in lines)
if not has_app:
lines.append('[App]')
lines.append(f'version = {version_str}')
else:
# 更新或追加version
new_lines = []
in_app = False
app_written = False
for l in lines:
if l.strip().lower() == '[app]':
in_app = True
new_lines.append(l)
continue
if in_app and l.strip().lower().startswith('version'):
new_lines.append(f'version = {version_str}')
app_written = True
in_app = True
continue
new_lines.append(l)
if not app_written:
new_lines.append('version = ' + version_str)
lines = new_lines
root_cfg.write_text('\n'.join(lines), encoding='utf-8')
print(f"已写入版本号: {version_str}")
except Exception as e:
print(f"版本信息注入失败: {e}")
result = subprocess.run([
'pyinstaller',
'OCR订单处理系统.spec'
], check=True, capture_output=True, text=True)
print("构建成功!")
print(result.stdout)
# 构建完成后,复制完整的配置文件到dist目录
dist_dir = Path('dist')
# 复制包含API密钥的配置文件
config_file = Path('config/config.ini')
if config_file.exists():
# 确保config目录存在
(dist_dir / 'config').mkdir(exist_ok=True)
shutil.copy2(config_file, dist_dir / 'config')
print(f"已复制配置文件到dist: {config_file} -> {dist_dir / 'config'}")
# 复制完整的条码映射文件
barcode_mapping_file = Path('config/barcode_mappings.json')
if barcode_mapping_file.exists():
shutil.copy2(barcode_mapping_file, dist_dir / 'config')
print(f"已复制条码映射文件到dist: {barcode_mapping_file} -> {dist_dir / 'config'}")
# 复制根目录的config.ini文件(覆盖空的配置文件)
root_config_file = Path('config.ini')
if root_config_file.exists():
shutil.copy2(root_config_file, dist_dir)
print(f"已复制根配置文件到dist: {root_config_file} -> {dist_dir}")
else:
print("警告: 根配置文件不存在,将创建缺省版本")
(dist_dir / 'config.ini').write_text('[App]\nversion = dev\n', encoding='utf-8')
except subprocess.CalledProcessError as e:
print(f"构建失败: {e}")
print(f"错误输出: {e.stderr}")
return False
return True
def create_portable_package():
"""创建便携版打包"""
print("创建便携版打包...")
# 创建发布目录
release_dir = Path('release')
if release_dir.exists():
try:
shutil.rmtree(release_dir)
except Exception as e:
print(f"警告: 无法完全清理发布目录 (可能文件被占用): {e}")
# 如果目录还在,尝试清理能清理的部分
for item in release_dir.iterdir():
try:
if item.is_dir(): shutil.rmtree(item)
else: item.unlink()
except Exception: pass
release_dir.mkdir(exist_ok=True)
# 复制exe文件
exe_file = Path('dist/OCR订单处理系统.exe')
if exe_file.exists():
shutil.copy2(exe_file, release_dir)
print(f"已复制: {exe_file} -> {release_dir}")
# 创建必要的目录结构
dirs_to_create = ['data/input', 'data/output', 'logs', 'templates', 'config']
for dir_path in dirs_to_create:
(release_dir / dir_path).mkdir(parents=True, exist_ok=True)
print(f"已创建目录: {dir_path}")
# 复制配置文件(包含API密钥)
config_file = Path('config/config.ini')
if config_file.exists():
shutil.copy2(config_file, release_dir / 'config')
print(f"已复制配置文件: {config_file} -> {release_dir / 'config'}")
else:
print(f"警告: 配置文件不存在: {config_file}")
# 复制完整的条码映射文件
barcode_mapping_file = Path('config/barcode_mappings.json')
if barcode_mapping_file.exists():
shutil.copy2(barcode_mapping_file, release_dir / 'config')
print(f"已复制条码映射文件: {barcode_mapping_file} -> {release_dir / 'config'}")
else:
print(f"警告: 条码映射文件不存在: {barcode_mapping_file}")
# 复制根目录的config.ini文件
root_config_file = Path('config.ini')
if root_config_file.exists():
shutil.copy2(root_config_file, release_dir)
print(f"已复制根配置文件: {root_config_file} -> {release_dir}")
else:
print(f"警告: 根配置文件不存在: {root_config_file}")
# 复制模板文件
template_file = Path('templates/银豹-采购单模板.xls')
if template_file.exists():
shutil.copy2(template_file, release_dir / 'templates')
print(f"已复制模板文件: {template_file} -> {release_dir / 'templates'}")
else:
print(f"警告: 模板文件不存在: {template_file}")
item_file = Path('templates/商品资料.xlsx')
if item_file.exists():
try:
(Path('dist') / 'templates').mkdir(exist_ok=True)
shutil.copy2(item_file, Path('dist') / 'templates')
except Exception:
pass
shutil.copy2(item_file, release_dir / 'templates')
print(f"已复制商品资料: {item_file} -> {release_dir / 'templates'}")
else:
print(f"警告: 商品资料文件不存在: {item_file}")
# 创建README文件
readme_content = '''
# OCR订单处理系统 - 便携版
## 使用说明
1. 双击 "OCR订单处理系统.exe" 启动程序
2. 将需要处理的图片文件放入 data/input 目录
3. 处理结果将保存在 data/output 目录
4. 日志文件保存在 logs 目录
## 注意事项
- 首次运行时需要配置百度OCR API密钥
- 支持的图片格式:jpg, jpeg, png, bmp
- 单个文件大小不超过4MB
## 目录结构
- OCR订单处理系统.exe - 主程序
- data/input/ - 输入图片目录
- data/output/ - 输出结果目录
- logs/ - 日志目录
'''
with open(release_dir / 'README.txt', 'w', encoding='utf-8') as f:
f.write(readme_content)
print("已创建README.txt")
print(f"便携版打包完成,位置: {release_dir.absolute()}")
def main():
"""主函数"""
print("=" * 50)
print("OCR订单处理系统 - EXE打包工具")
print("=" * 50)
# 检查是否安装了PyInstaller
try:
subprocess.run(['pyinstaller', '--version'], check=True, capture_output=True)
except (subprocess.CalledProcessError, FileNotFoundError):
print("错误: 未安装PyInstaller")
print("请运行: pip install pyinstaller")
return 1
# 清理构建目录
clean_build()
# 创建spec文件
create_spec_file()
# 构建EXE
if not build_exe():
return 1
# 创建便携版打包
create_portable_package()
print("\n" + "=" * 50)
print("打包完成!")
print("EXE文件位置: dist/OCR订单处理系统.exe")
print("便携版位置: release/")
print("=" * 50)
return 0
if __name__ == '__main__':
sys.exit(main())