335 lines
11 KiB
Python
335 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/'),
|
|
]
|
|
|
|
# 需要隐式导入的模块
|
|
hidden_imports = [
|
|
'tkinter',
|
|
'tkinter.ttk',
|
|
'tkinter.filedialog',
|
|
'tkinter.messagebox',
|
|
'tkinter.scrolledtext',
|
|
'pandas',
|
|
'numpy',
|
|
'openpyxl',
|
|
'xlrd',
|
|
'xlwt',
|
|
'xlutils',
|
|
'xlutils.copy',
|
|
'requests',
|
|
'dotenv',
|
|
'tkinterdnd2',
|
|
'configparser',
|
|
'threading',
|
|
'datetime',
|
|
'json',
|
|
're',
|
|
'subprocess',
|
|
'shutil',
|
|
'sqlite3',
|
|
'logging',
|
|
'base64',
|
|
'concurrent.futures',
|
|
'pathlib',
|
|
'typing',
|
|
]
|
|
|
|
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("更新便携版打包...")
|
|
|
|
# 1. 准备本地 release 目录
|
|
release_dir = Path('release')
|
|
|
|
# 不再删除整个目录,以保留 data/input 等用户数据
|
|
if not release_dir.exists():
|
|
release_dir.mkdir(parents=True)
|
|
print(f"已创建本地发布目录: {release_dir}")
|
|
else:
|
|
print(f"本地发布目录已存在,将进行增量更新: {release_dir}")
|
|
|
|
# 2. 更新核心文件到本地 release
|
|
# 复制exe文件
|
|
exe_file = Path('dist/OCR订单处理系统.exe')
|
|
if exe_file.exists():
|
|
shutil.copy2(exe_file, release_dir)
|
|
print(f"已更新 EXE: {exe_file} -> {release_dir}")
|
|
|
|
# 确保必要的子目录存在
|
|
dirs_to_ensure = ['data/input', 'data/output', 'data/result', 'logs', 'templates', 'config']
|
|
for dir_path in dirs_to_ensure:
|
|
(release_dir / dir_path).mkdir(parents=True, exist_ok=True)
|
|
|
|
# 复制配置文件
|
|
files_to_copy = [
|
|
(Path('config/config.ini'), release_dir / 'config'),
|
|
(Path('config/barcode_mappings.json'), release_dir / 'config'),
|
|
(Path('config.ini'), release_dir),
|
|
(Path('templates/银豹-采购单模板.xls'), release_dir / 'templates'),
|
|
(Path('templates/商品资料.xlsx'), release_dir / 'templates'),
|
|
]
|
|
|
|
for src, dst_dir in files_to_copy:
|
|
if src.exists():
|
|
dst_dir.mkdir(parents=True, exist_ok=True)
|
|
shutil.copy2(src, dst_dir)
|
|
print(f"已更新文件: {src} -> {dst_dir}")
|
|
else:
|
|
print(f"警告: 源文件不存在,跳过更新: {src}")
|
|
|
|
# 3. 创建/更新 README
|
|
readme_content = '''
|
|
# OCR订单处理系统 - 便携版
|
|
|
|
## 使用说明
|
|
1. 双击 "OCR订单处理系统.exe" 启动程序
|
|
2. 将需要处理的图片文件放入 data/input 目录
|
|
3. 处理结果将保存在 data/result 目录 (采购单 Excel)
|
|
4. 中间过程文件在 data/output 目录
|
|
5. 日志文件保存在 logs 目录
|
|
|
|
## 注意事项
|
|
- 支持的图片格式:jpg, jpeg, png, bmp
|
|
- 单个文件大小建议不超过 4MB
|
|
|
|
## 目录结构
|
|
- OCR订单处理系统.exe - 主程序
|
|
- data/input/ - 输入图片目录
|
|
- data/result/ - 最终采购单目录
|
|
- logs/ - 日志目录
|
|
'''
|
|
with open(release_dir / 'README.txt', 'w', encoding='utf-8') as f:
|
|
f.write(readme_content)
|
|
|
|
# 4. 同步到桌面
|
|
try:
|
|
# 用户指定的特殊桌面路径
|
|
desktop_path = Path(r"F:\Administrator\桌面")
|
|
if not desktop_path.exists():
|
|
# 兜底:如果 F 盘路径不存在,尝试系统默认路径
|
|
desktop_path = Path(os.path.join(os.path.expanduser("~"), "Desktop"))
|
|
|
|
desktop_release = desktop_path / "OCR系统_Release"
|
|
|
|
print(f"正在同步到桌面: {desktop_release}")
|
|
|
|
# 使用自定义的同步逻辑,避免删除目标目录中的其他文件
|
|
def sync_dir(src_root, dst_root):
|
|
if not dst_root.exists():
|
|
dst_root.mkdir(parents=True)
|
|
|
|
for item in src_root.iterdir():
|
|
dst_item = dst_root / item.name
|
|
if item.is_dir():
|
|
sync_dir(item, dst_item)
|
|
else:
|
|
# 如果是文件,直接覆盖更新
|
|
shutil.copy2(item, dst_item)
|
|
|
|
sync_dir(release_dir, desktop_release)
|
|
print(f"同步成功!桌面位置: {desktop_release.absolute()}")
|
|
|
|
except Exception as e:
|
|
print(f"同步到桌面失败: {e}")
|
|
|
|
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()) |