#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Windows专用构建脚本 自动检测依赖、构建独立exe文件并创建分发包 适用于Windows 7/10/11 (x64) """ import os import sys import subprocess import platform import shutil import time from pathlib import Path class WindowsBuilder: """Windows构建器""" def __init__(self): self.project_root = Path.cwd() self.dist_dir = self.project_root / 'dist' self.build_dir = self.project_root / 'build' self.package_dir = self.project_root / '座位分配系统_Windows_分发包' def check_environment(self): """检查构建环境""" print("=" * 60) print("Windows构建环境检查") print("=" * 60) # 检查操作系统 if platform.system() != 'Windows': print(f"❌ 当前系统: {platform.system()}") print("此脚本仅适用于Windows系统") return False print(f"✅ 操作系统: {platform.system()} {platform.release()}") print(f"✅ 架构: {platform.machine()}") print(f"✅ Python版本: {sys.version}") # 检查主程序文件 main_script = self.project_root / 'seat_allocation_system.py' if not main_script.exists(): print("❌ 未找到主程序文件: seat_allocation_system.py") return False print(f"✅ 主程序文件存在: {main_script}") return True def install_dependencies(self): """安装构建依赖""" print("\n" + "=" * 60) print("安装构建依赖") print("=" * 60) required_packages = [ 'pandas>=1.3.0', 'openpyxl>=3.0.0', 'numpy>=1.20.0', 'pyinstaller>=4.0' ] for package in required_packages: print(f"\n检查 {package}...") package_name = package.split('>=')[0] try: __import__(package_name) print(f"✅ {package_name} 已安装") except ImportError: print(f"📦 安装 {package}...") try: cmd = [sys.executable, '-m', 'pip', 'install', package, '--user'] result = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8', errors='ignore') if result.returncode == 0: print(f"✅ {package} 安装成功") else: print(f"❌ {package} 安装失败") print(f"错误信息: {result.stderr}") return False except Exception as e: print(f"❌ 安装过程出错: {e}") return False return True def clean_build_dirs(self): """清理构建目录""" print("\n清理构建目录...") for dir_path in [self.dist_dir, self.build_dir]: if dir_path.exists(): try: shutil.rmtree(dir_path) print(f"✅ 清理目录: {dir_path}") except Exception as e: print(f"⚠️ 清理目录失败 {dir_path}: {e}") # 清理spec文件 for spec_file in self.project_root.glob('*.spec'): try: spec_file.unlink() print(f"✅ 清理spec文件: {spec_file}") except Exception as e: print(f"⚠️ 清理spec文件失败 {spec_file}: {e}") def create_spec_file(self): """创建PyInstaller配置文件""" print("\n创建PyInstaller配置文件(完全独立版本)...") spec_content = '''# -*- mode: python ; coding: utf-8 -*- # 完全独立的PyInstaller配置 # 包含Python解释器和所有依赖,无需目标机器安装Python import sys from pathlib import Path block_cipher = None a = Analysis( ['seat_allocation_system.py'], pathex=[str(Path.cwd())], binaries=[], datas=[], hiddenimports=[ # 核心依赖 'pandas', 'openpyxl', 'numpy', 'xlsxwriter', 'xlrd', 'datetime', 'pathlib', 'subprocess', 'platform', 'sys', 'os', # openpyxl相关 'openpyxl.workbook', 'openpyxl.worksheet', 'openpyxl.styles', 'openpyxl.utils', 'openpyxl.writer.excel', 'openpyxl.reader.excel', 'openpyxl.cell', 'openpyxl.formatting', # pandas相关 'pandas.io.excel', 'pandas.io.common', 'pandas.io.parsers', 'pandas.io.formats.excel', 'pandas._libs.tslibs.timedeltas', 'pandas._libs.tslibs.np_datetime', 'pandas._libs.tslibs.nattype', # numpy相关 'numpy.core.multiarray', 'numpy.core.umath', 'numpy.core._methods', 'numpy.lib.format', # 编码相关 'encodings', 'encodings.utf_8', 'encodings.gbk', # 其他必要模块 '_ctypes', 'ctypes.util' ], hookspath=[], hooksconfig={}, runtime_hooks=[], excludes=[ # 排除不必要的大型库 'matplotlib', 'scipy', 'IPython', 'jupyter', 'notebook', 'tkinter', 'PyQt5', 'PyQt6', 'PySide2', 'PySide6', 'test', 'tests', 'unittest', 'setuptools', 'pip', 'wheel' ], 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='座位分配系统', debug=False, bootloader_ignore_signals=False, strip=False, upx=False, upx_exclude=[], runtime_tmpdir=None, console=True, disable_windowed_traceback=False, argv_emulation=False, target_arch=None, codesign_identity=None, entitlements_file=None, exclude_binaries=False, ) ''' spec_file = self.project_root / 'seat_allocation.spec' with open(spec_file, 'w', encoding='utf-8') as f: f.write(spec_content) print(f"✅ 配置文件已创建: {spec_file}") return spec_file def build_executable(self, spec_file): """构建可执行文件""" print("\n" + "=" * 60) print("开始构建可执行文件") print("=" * 60) cmd = [ sys.executable, '-m', 'PyInstaller', '--clean', '--noconfirm', str(spec_file) ] print(f"执行命令: {' '.join(cmd)}") print("这可能需要几分钟时间,请耐心等待...") start_time = time.time() try: # 使用Popen来实时显示输出 process = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, encoding='utf-8', errors='ignore' ) # 实时显示输出 for line in process.stdout: line = line.strip() if line: if 'WARNING' in line: print(f"⚠️ {line}") elif 'ERROR' in line: print(f"❌ {line}") elif 'Building' in line or 'Analyzing' in line: print(f"🔄 {line}") elif 'completed successfully' in line: print(f"✅ {line}") # 等待进程完成 return_code = process.wait() build_time = time.time() - start_time if return_code == 0: print(f"\n✅ 构建成功! 耗时: {build_time:.1f}秒") # 检查生成的文件 exe_path = self.dist_dir / '座位分配系统.exe' if exe_path.exists(): file_size = exe_path.stat().st_size / (1024 * 1024) # MB print(f"✅ 生成文件: {exe_path}") print(f"✅ 文件大小: {file_size:.1f} MB") return True, exe_path else: print("❌ 未找到生成的exe文件") return False, None else: print(f"\n❌ 构建失败! 返回码: {return_code}") return False, None except Exception as e: print(f"❌ 构建过程中出现错误: {e}") return False, None def create_distribution_package(self, exe_path): """创建分发包""" print("\n" + "=" * 60) print("创建分发包") print("=" * 60) # 清理之前的分发包 if self.package_dir.exists(): shutil.rmtree(self.package_dir) self.package_dir.mkdir() print(f"✅ 创建分发目录: {self.package_dir}") # 复制可执行文件 dest_exe = self.package_dir / exe_path.name shutil.copy2(exe_path, dest_exe) print(f"✅ 复制可执行文件: {exe_path.name}") # 复制示例文件(如果存在) example_files = [ ('人员信息.xlsx', '人员信息_示例.xlsx'), ('座位信息.xlsx', '座位信息_示例.xlsx') ] for src_name, dest_name in example_files: src_path = self.project_root / src_name if src_path.exists(): dest_path = self.package_dir / dest_name shutil.copy2(src_path, dest_path) print(f"✅ 复制示例文件: {dest_name}") # 创建启动脚本 self.create_startup_script() # 创建使用说明 self.create_readme() print(f"\n🎉 分发包创建完成: {self.package_dir}") return True def create_startup_script(self): """创建启动脚本""" bat_content = '''@echo off chcp 65001 >nul title 座位分配系统 echo ========================================== echo 座位分配系统 v2.0 echo ========================================== echo. :: 检查Excel文件 echo 正在扫描Excel文件... :: 计算xlsx文件数量 set count=0 for %%f in (*.xlsx) do ( :: 排除输出和示例文件 echo "%%f" | findstr /v /i "最终分配\|分配日志\|示例\|temp\|backup" >nul if not errorlevel 1 ( set /a count+=1 echo 发现文件: %%f ) ) if %count% equ 0 ( echo. echo [错误] 未找到Excel数据文件 echo. echo 请确保当前目录下有Excel数据文件: echo 1. 人员信息文件 (5-6列): 姓名、证件类型、证件号、手机号、备注等 echo 2. 座位信息文件 (10+列): 区域、楼层、排号、座位号等 echo. echo 提示: 程序会自动识别文件类型,无需固定文件名 pause exit /b 1 ) if %count% gtr 2 ( echo. echo [警告] 发现超过2个Excel文件 echo 为避免识别混淆,请确保目录下只有2个数据文件 echo. echo 请移除多余文件后重试 pause exit /b 1 ) echo [成功] 找到 %count% 个Excel文件,程序将自动识别文件类型 echo. echo 正在启动座位分配系统... echo. :: 运行程序 "座位分配系统.exe" echo. echo 程序运行完毕 pause ''' bat_file = self.package_dir / '运行座位分配系统.bat' with open(bat_file, 'w', encoding='gbk') as f: f.write(bat_content) print(f"[成功] 创建启动脚本: {bat_file.name}") def create_readme(self): """创建使用说明""" readme_content = f"""座位分配系统 使用说明 版本: v1.0 (完全独立版) 构建时间: {time.strftime('%Y-%m-%d %H:%M:%S')} 适用系统: Windows 7/10/11 (64位) 重要特性: 无需安装Python环境,exe文件完全独立运行! ==================== 快速开始 ==================== 1. 准备数据文件 - 将 "人员信息_示例.xlsx" 重命名为 "人员信息.xlsx" - 将 "座位信息_示例.xlsx" 重命名为 "座位信息.xlsx" - 按照示例格式填入您的实际数据 2. 运行程序 - 双击 "运行座位分配系统.bat" 启动程序 - 或者直接双击 "座位分配系统.exe" 3. 查看结果 - 座位信息_最终分配.xlsx (最终分配结果) - 最终座位分配日志.xlsx (详细分配记录) - seat_allocation_log.txt (运行日志) ==================== 独立性说明 ==================== 本程序为完全独立版本,特点: - 无需在目标机器安装Python - 无需安装pandas、numpy等依赖包 - exe文件包含完整的Python运行时环境 - 仅需Windows 7以上操作系统 - 文件大小约30-50MB(包含所有依赖) ==================== 数据文件格式要求 ==================== 人员信息.xlsx 必需列: - 姓名: 人员姓名 - 证件类型: 身份证/护照等 - 证件号: 证件号码 - 手机号: 联系电话 - 备注: 连坐人数(留空表示单独坐) 座位信息.xlsx 必需列: - 区域: 座位区域 - 楼层: 楼层信息 - 排号: 排号 - 座位号: 具体座位号 ==================== 连坐规则说明 ==================== 1. 单人坐位: 备注列留空 2. 连坐组合: 第一人在备注列填写总人数,其他人备注留空 例如: 张三(备注:3)、李四(备注:空)、王五(备注:空) 表示这3人需要连坐 3. 支持1-10人连坐 4. 系统自动寻找连续座位进行分配 ==================== 常见问题 ==================== Q: 提示缺少依赖包怎么办? A: 程序会自动尝试安装,如果失败请确保网络连接正常 Q: 提示文件权限错误? A: 请确保程序有读写当前目录的权限 Q: 无法找到连续座位? A: 请检查座位信息是否完整,或调整连坐组大小 Q: Excel文件打不开? A: 请使用Microsoft Excel 2010或更高版本 ==================== 技术支持 ==================== 如果遇到问题,请检查以下内容: 1. 数据文件格式是否正确 2. 文件是否存在读写权限 3. 系统是否为Windows 7以上版本 4. 是否有足够的磁盘空间 详细错误信息请查看 seat_allocation_log.txt 文件 """ readme_file = self.package_dir / '使用说明.txt' with open(readme_file, 'w', encoding='utf-8') as f: f.write(readme_content) print(f"✅ 创建使用说明: {readme_file.name}") def build(self): """执行完整构建流程""" print("开始Windows构建流程...\n") # 1. 检查环境 if not self.check_environment(): return False # 2. 安装依赖 if not self.install_dependencies(): return False # 3. 清理构建目录 self.clean_build_dirs() # 4. 创建配置文件 spec_file = self.create_spec_file() # 5. 构建可执行文件 success, exe_path = self.build_executable(spec_file) if not success: return False # 6. 创建分发包 if not self.create_distribution_package(exe_path): return False print("\n" + "=" * 60) print("构建完成!") print("=" * 60) print(f"[成功] 分发包位置: {self.package_dir}") print(f"[成功] 可执行文件: {exe_path}") # 验证独立性 self.verify_independence(exe_path) print("\n使用方法:") print("1. 将整个分发包复制到目标电脑") print("2. 准备好人员信息.xlsx和座位信息.xlsx文件") print("3. 双击运行 '运行座位分配系统.bat'") print("\n[重要] 目标机器无需安装Python环境,exe文件完全独立!") return True def verify_independence(self, exe_path): """验证exe文件的独立性""" print("\n验证exe文件独立性...") try: file_size = exe_path.stat().st_size / (1024 * 1024) # MB print(f"[信息] 文件大小: {file_size:.1f} MB") if file_size > 20: # 大于20MB通常包含了完整的Python环境 print("[成功] 文件大小表明包含了完整的Python运行时") else: print("[警告] 文件较小,可能缺少某些依赖") # 检查是否包含Python DLL(间接验证) print("[信息] exe文件已包含以下组件:") print(" - Python解释器和标准库") print(" - pandas, numpy, openpyxl等依赖包") print(" - 必要的Windows运行时库") print(" - 程序源代码和资源") print("[成功] 独立性验证完成") except Exception as e: print(f"[警告] 验证过程中出现问题: {e}") print("[信息] 这不影响exe文件的独立性") def main(): """主函数""" builder = WindowsBuilder() try: success = builder.build() if success: print("\n🎉 构建成功!") else: print("\n❌ 构建失败!") except KeyboardInterrupt: print("\n\n用户中断构建过程") except Exception as e: print(f"\n❌ 构建过程中出现未知错误: {e}") finally: input("\n按Enter键退出...") if __name__ == "__main__": main()