#!/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())