336 lines
11 KiB
Python
336 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',
|
||
'configparser',
|
||
'threading',
|
||
'datetime',
|
||
'json',
|
||
're',
|
||
'subprocess',
|
||
'shutil',
|
||
'app.config.settings',
|
||
'app.services.ocr_service',
|
||
'app.services.order_service',
|
||
'app.services.tobacco_service',
|
||
'app.core.utils.dialog_utils',
|
||
'app.core.excel.converter',
|
||
]
|
||
|
||
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()) |