496 lines
14 KiB
Python
496 lines
14 KiB
Python
#!/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 -*-
|
|
|
|
block_cipher = None
|
|
|
|
a = Analysis(
|
|
['seat_allocation_system.py'],
|
|
pathex=[],
|
|
binaries=[],
|
|
datas=[],
|
|
hiddenimports=[
|
|
'pandas',
|
|
'openpyxl',
|
|
'numpy',
|
|
'xlsxwriter',
|
|
'xlrd',
|
|
'datetime',
|
|
'pathlib',
|
|
'openpyxl.workbook',
|
|
'openpyxl.worksheet',
|
|
'openpyxl.styles'
|
|
],
|
|
hookspath=[],
|
|
hooksconfig={},
|
|
runtime_hooks=[],
|
|
excludes=[
|
|
'matplotlib',
|
|
'scipy',
|
|
'IPython',
|
|
'jupyter',
|
|
'notebook',
|
|
'tkinter',
|
|
'PyQt5',
|
|
'PyQt6',
|
|
'PySide2',
|
|
'PySide6'
|
|
],
|
|
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,
|
|
)
|
|
'''
|
|
|
|
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 座位分配系统 v1.0
|
|
echo ==========================================
|
|
echo.
|
|
|
|
:: 检查数据文件
|
|
if not exist "人员信息.xlsx" (
|
|
echo ❌ 缺少文件: 人员信息.xlsx
|
|
echo.
|
|
echo 请将 人员信息_示例.xlsx 重命名为 人员信息.xlsx
|
|
echo 并按照格式填入您的数据
|
|
echo.
|
|
pause
|
|
exit /b 1
|
|
)
|
|
|
|
if not exist "座位信息.xlsx" (
|
|
echo ❌ 缺少文件: 座位信息.xlsx
|
|
echo.
|
|
echo 请将 座位信息_示例.xlsx 重命名为 座位信息.xlsx
|
|
echo 并按照格式填入您的数据
|
|
echo.
|
|
pause
|
|
exit /b 1
|
|
)
|
|
|
|
echo ✅ 数据文件检查通过
|
|
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位)
|
|
|
|
====================
|
|
快速开始
|
|
====================
|
|
|
|
1. 准备数据文件
|
|
- 将 "人员信息_示例.xlsx" 重命名为 "人员信息.xlsx"
|
|
- 将 "座位信息_示例.xlsx" 重命名为 "座位信息.xlsx"
|
|
- 按照示例格式填入您的实际数据
|
|
|
|
2. 运行程序
|
|
- 双击 "运行座位分配系统.bat" 启动程序
|
|
- 或者直接双击 "座位分配系统.exe"
|
|
|
|
3. 查看结果
|
|
- 座位信息_最终分配.xlsx (最终分配结果)
|
|
- 最终座位分配日志.xlsx (详细分配记录)
|
|
- seat_allocation_log.txt (运行日志)
|
|
|
|
====================
|
|
数据文件格式要求
|
|
====================
|
|
|
|
人员信息.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}")
|
|
print("\n使用方法:")
|
|
print("1. 将整个分发包复制到目标电脑")
|
|
print("2. 准备好人员信息.xlsx和座位信息.xlsx文件")
|
|
print("3. 双击运行 '运行座位分配系统.bat'")
|
|
|
|
return True
|
|
|
|
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() |