Compare commits
15 Commits
e66a6cec84
...
master
Author | SHA1 | Date | |
---|---|---|---|
1e9925a9f2 | |||
8a103224b5 | |||
22b17a1d5d | |||
c0568cc1ec | |||
02341150a9 | |||
f52df5e42d | |||
fcacab52c2 | |||
f1e8d94266 | |||
9c3470e60b | |||
95897979c7 | |||
b9aa686004 | |||
b437f3ffc1 | |||
18d7a4d0a1 | |||
0336d55682 | |||
84f11a0906 |
108
BUILD_FIXES.md
Normal file
108
BUILD_FIXES.md
Normal file
@@ -0,0 +1,108 @@
|
||||
# Windows构建问题修复指南
|
||||
|
||||
## 🔧 已修复的问题
|
||||
|
||||
### 1. Hidden import 'numpy.core._methods' not found
|
||||
**解决方案**: 在spec文件中添加了完整的numpy隐藏导入
|
||||
```python
|
||||
'numpy.core._methods',
|
||||
'numpy.core._dtype_ctypes',
|
||||
'numpy.core._internal',
|
||||
```
|
||||
|
||||
### 2. Hidden import "jinja2" not found
|
||||
**解决方案**: 添加了jinja2相关导入
|
||||
```python
|
||||
'jinja2',
|
||||
'jinja2.ext',
|
||||
'markupsafe',
|
||||
```
|
||||
|
||||
### 3. Library not found: msvcp140-1a0962f2a91a74c6d7136a768987a591.dll
|
||||
**解决方案**:
|
||||
- 禁用UPX压缩 (`upx=False`)
|
||||
- 添加pandas完整依赖收集
|
||||
- 使用`--collect-all=pandas`选项
|
||||
|
||||
## 🚀 使用修复后的构建脚本
|
||||
|
||||
### 方法1: 简化构建(推荐)
|
||||
```bash
|
||||
# Windows批处理
|
||||
build_simple.bat
|
||||
|
||||
# 或直接运行Python脚本
|
||||
python simple_windows_build.py
|
||||
```
|
||||
|
||||
### 方法2: 完整构建
|
||||
```bash
|
||||
# 先测试环境
|
||||
python test_build.py
|
||||
|
||||
# 运行完整构建
|
||||
python windows_build.py
|
||||
```
|
||||
|
||||
## ⚠️ 重要修复
|
||||
|
||||
### PyInstaller选项冲突问题
|
||||
**问题**: `option(s) not allowed` 错误
|
||||
**原因**: 使用spec文件时不能同时使用某些命令行选项
|
||||
**解决**: 将所有配置写入spec文件,命令行只保留基本选项
|
||||
|
||||
```python
|
||||
# ❌ 错误用法
|
||||
cmd = ['pyinstaller', '--collect-all=pandas', 'file.spec']
|
||||
|
||||
# ✅ 正确用法
|
||||
cmd = ['pyinstaller', '--clean', '--noconfirm', 'file.spec']
|
||||
```
|
||||
|
||||
## 📋 构建选项说明
|
||||
|
||||
### PyInstaller命令行选项
|
||||
- `--clean`: 清理缓存
|
||||
- `--noconfirm`: 不询问确认
|
||||
- `--log-level=INFO`: 详细日志
|
||||
- `--collect-all=pandas`: 收集pandas所有模块
|
||||
- `--collect-all=numpy`: 收集numpy所有模块
|
||||
- `--collect-all=openpyxl`: 收集openpyxl所有模块
|
||||
|
||||
### 关键隐藏导入
|
||||
```python
|
||||
'numpy.core._methods',
|
||||
'pandas._libs.window.aggregations',
|
||||
'ctypes.util',
|
||||
'pkg_resources.py2_warn',
|
||||
```
|
||||
|
||||
## ⚠️ 常见问题
|
||||
|
||||
### 1. 构建时间长
|
||||
- 正常现象,首次构建可能需要5-10分钟
|
||||
- 后续构建会使用缓存,速度更快
|
||||
|
||||
### 2. 文件大小大
|
||||
- 包含完整Python环境和所有依赖
|
||||
- 可以通过排除不必要的模块来减小大小
|
||||
|
||||
### 3. 警告信息
|
||||
- 大部分WARNING可以忽略
|
||||
- 只要没有ERROR,构建通常能成功
|
||||
|
||||
## 🎯 构建成功标志
|
||||
|
||||
```
|
||||
✅ 构建成功! 耗时: XX.X秒
|
||||
✅ 生成文件: dist/座位分配系统.exe
|
||||
✅ 文件大小: XX.X MB
|
||||
```
|
||||
|
||||
## 📦 分发包内容
|
||||
|
||||
构建成功后会生成:
|
||||
- `座位分配系统_Windows_分发包/`
|
||||
- `座位分配系统.exe` - 主程序
|
||||
- `README.txt` - 使用说明
|
||||
- `示例文件/` - 示例Excel文件(如果存在)
|
52
README.md
52
README.md
@@ -5,22 +5,35 @@
|
||||
## 🎯 功能特点
|
||||
|
||||
- ✅ **智能分配**: 支持1-10人的各种连坐需求
|
||||
- ✅ **数据校验**: 完整的文件格式和逻辑校验
|
||||
- ✅ **智能识别**: 自动识别Excel文件类型,支持默认文件名和智能识别两种模式
|
||||
- ✅ **双重分组**: 支持备注分组和手机号分组两种模式
|
||||
- ✅ **数据校验**: 完整的文件格式和逻辑校验,支持重名+身份证号校验
|
||||
- ✅ **不连续座位**: 自动处理座位号间隙
|
||||
- ✅ **详细日志**: 完整的操作记录和结果验证
|
||||
- ✅ **跨平台**: 支持Windows、macOS、Linux
|
||||
- ✅ **智能覆盖**: 人员信息覆盖座位表对应列,分配备注单独存储在第13列
|
||||
|
||||
## 📋 使用方法
|
||||
|
||||
### 准备Excel文件
|
||||
|
||||
1. **人员信息.xlsx** - 包含以下列:
|
||||
1. **人员信息文件** - 包含以下列:
|
||||
- 姓名、证件类型、证件号、手机号、备注
|
||||
- 备注数字表示连坐人数(如:备注4表示当前行+后3行共4人连坐)
|
||||
- **备注模式**: 备注数字表示连坐人数(如:备注4表示当前行+后3行共4人连坐)
|
||||
- **手机号模式**: 相同手机号的人员自动识别为连坐组
|
||||
|
||||
2. **座位信息.xlsx** - 包含以下列:
|
||||
2. **座位信息文件** - 包含以下列:
|
||||
- 区域、楼层、排号、座位号
|
||||
|
||||
### 文件识别方式
|
||||
|
||||
程序启动时会提供两种文件识别方式:
|
||||
1. **默认文件名** - 读取固定文件名 `人员信息.xlsx` 和 `座位信息.xlsx`
|
||||
2. **智能识别** - 自动分析目录中的Excel文件并识别类型
|
||||
- 根据列名特征自动判断文件类型
|
||||
- 支持任意文件名(如 `1.xlsx`、`2.xlsx`)
|
||||
- 提供手动选择功能作为备选
|
||||
|
||||
### 运行程序
|
||||
|
||||
#### Python环境运行
|
||||
@@ -41,12 +54,28 @@ python simple_build.py
|
||||
python cross_platform_build.py
|
||||
```
|
||||
|
||||
### 选择文件识别模式
|
||||
|
||||
程序运行时首先选择文件识别模式:
|
||||
1. **默认文件名** - 读取 `人员信息.xlsx` 和 `座位信息.xlsx`
|
||||
2. **智能识别** - 自动识别目录中的Excel文件
|
||||
|
||||
### 选择分组方式
|
||||
|
||||
然后选择连坐分组方式:
|
||||
1. **备注分组** - 根据备注数字识别连坐组(默认)
|
||||
2. **手机号分组** - 根据相同手机号识别连坐组
|
||||
|
||||
### 查看结果
|
||||
|
||||
程序运行后会生成:
|
||||
- `座位信息_最终分配.xlsx` - 最终分配结果
|
||||
- `最终座位分配日志.xlsx` - 详细分配记录
|
||||
- `seat_allocation_log.txt` - 完整运行日志
|
||||
- `log/座位信息_最终分配.xlsx` - 最终分配结果
|
||||
- 第1-5列:座位基本信息(保持不变)
|
||||
- 第6-10列:人员信息(覆盖原座位表数据)
|
||||
- 第11-12列:原座位表其他列(保持不变)
|
||||
- 第13列:分配备注(新增列)
|
||||
- `log/最终座位分配日志.xlsx` - 详细分配记录
|
||||
- `log/seat_allocation_log.txt` - 完整运行日志
|
||||
|
||||
## 🔧 构建选项
|
||||
|
||||
@@ -117,9 +146,12 @@ A区通道 | 一层 | 1排 | 3号
|
||||
## ⚠️ 注意事项
|
||||
|
||||
1. **备注逻辑**: 备注数字表示连坐人数,只有组长填写备注,成员留空
|
||||
2. **证件号格式**: 支持包含X的身份证号,自动处理为字符串格式
|
||||
3. **文字清理**: 自动清除姓名等字段的多余空格
|
||||
4. **座位连续性**: 支持不连续座位号,算法会自动寻找合适的连续段
|
||||
2. **手机号逻辑**: 相同手机号的人员会被识别为连坐组
|
||||
3. **重名处理**: 支持重名人员,通过姓名+身份证号组合进行唯一性校验
|
||||
4. **证件号格式**: 支持包含X的身份证号,自动处理为字符串格式
|
||||
5. **文字清理**: 自动清除姓名等字段的多余空格
|
||||
6. **座位连续性**: 支持不连续座位号,算法会自动寻找合适的连续段
|
||||
7. **智能覆盖**: 人员信息覆盖座位表对应列,分配备注单独存储
|
||||
|
||||
## 🔍 故障排除
|
||||
|
||||
|
31
build_simple.bat
Normal file
31
build_simple.bat
Normal file
@@ -0,0 +1,31 @@
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
echo ============================================================
|
||||
echo 简化Windows构建脚本
|
||||
echo ============================================================
|
||||
echo.
|
||||
|
||||
echo 检查Python环境...
|
||||
python --version
|
||||
if errorlevel 1 (
|
||||
echo ❌ Python未安装或未添加到PATH
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo.
|
||||
echo 检查依赖包...
|
||||
python -c "import pandas, numpy, openpyxl, PyInstaller; print('✅ 所有依赖已安装')"
|
||||
if errorlevel 1 (
|
||||
echo ❌ 缺少必要的依赖包
|
||||
echo 正在安装依赖...
|
||||
pip install pandas numpy openpyxl pyinstaller
|
||||
)
|
||||
|
||||
echo.
|
||||
echo 开始构建...
|
||||
python simple_windows_build.py
|
||||
|
||||
echo.
|
||||
echo 构建完成!
|
||||
pause
|
138
build_windows.bat
Normal file
138
build_windows.bat
Normal file
@@ -0,0 +1,138 @@
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
echo ================================
|
||||
echo 座位分配系统 Windows 打包脚本
|
||||
echo ================================
|
||||
echo.
|
||||
|
||||
:: 检查Python是否安装
|
||||
python --version >nul 2>&1
|
||||
if %errorlevel% neq 0 (
|
||||
echo [错误] Python未安装或不在PATH中
|
||||
echo 请先安装Python 3.7+
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [信息] 检查Python版本...
|
||||
python --version
|
||||
|
||||
:: 检查pip是否可用
|
||||
pip --version >nul 2>&1
|
||||
if %errorlevel% neq 0 (
|
||||
echo [错误] pip不可用
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
:: 安装PyInstaller
|
||||
echo.
|
||||
echo [步骤1] 安装PyInstaller...
|
||||
pip install pyinstaller
|
||||
|
||||
:: 检查PyInstaller是否安装成功
|
||||
pyinstaller --version >nul 2>&1
|
||||
if %errorlevel% neq 0 (
|
||||
echo [错误] PyInstaller安装失败
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
:: 创建build目录
|
||||
echo.
|
||||
echo [步骤2] 创建构建目录...
|
||||
if not exist "build" mkdir build
|
||||
if not exist "dist" mkdir dist
|
||||
|
||||
:: 安装依赖包
|
||||
echo.
|
||||
echo [步骤3] 安装依赖包...
|
||||
pip install pandas numpy openpyxl
|
||||
|
||||
:: 打包主程序
|
||||
echo.
|
||||
echo [步骤4] 开始打包座位分配系统...
|
||||
pyinstaller --onefile ^
|
||||
--console ^
|
||||
--name "座位分配系统" ^
|
||||
--icon=icon.ico ^
|
||||
--add-data "README.md;." ^
|
||||
--distpath "dist" ^
|
||||
--workpath "build" ^
|
||||
--specpath "build" ^
|
||||
--clean ^
|
||||
seat_allocation_system.py
|
||||
|
||||
:: 检查打包是否成功
|
||||
if exist "dist\座位分配系统.exe" (
|
||||
echo.
|
||||
echo [成功] 打包完成!
|
||||
echo 可执行文件位置: dist\座位分配系统.exe
|
||||
|
||||
:: 创建发布目录
|
||||
echo.
|
||||
echo [步骤5] 创建发布包...
|
||||
if not exist "release" mkdir release
|
||||
|
||||
:: 复制可执行文件
|
||||
copy "dist\座位分配系统.exe" "release\"
|
||||
|
||||
:: 创建示例文件夹
|
||||
if not exist "release\示例文件" mkdir "release\示例文件"
|
||||
|
||||
:: 复制示例文件(如果存在)
|
||||
if exist "人员信息_示例.xlsx" copy "人员信息_示例.xlsx" "release\示例文件\"
|
||||
if exist "座位信息_示例.xlsx" copy "座位信息_示例.xlsx" "release\示例文件\"
|
||||
|
||||
:: 创建使用说明
|
||||
echo.
|
||||
echo [步骤6] 创建使用说明...
|
||||
(
|
||||
echo 座位分配系统 v2.0
|
||||
echo ==================
|
||||
echo.
|
||||
echo 使用方法:
|
||||
echo 1. 将人员信息和座位信息的Excel文件放在程序同一目录下
|
||||
echo 2. 双击运行"座位分配系统.exe"
|
||||
echo 3. 按照提示操作
|
||||
echo.
|
||||
echo 文件要求:
|
||||
echo - 人员信息文件:包含姓名、证件类型、证件号、手机号、备注等列
|
||||
echo - 座位信息文件:包含区域、楼层、排号、座位号等列
|
||||
echo.
|
||||
echo 输出文件:
|
||||
echo - log/座位信息_最终分配.xlsx (分配结果)
|
||||
echo - log/最终座位分配日志.xlsx (详细日志)
|
||||
echo - log/seat_allocation_log.txt (运行日志)
|
||||
echo.
|
||||
echo 注意事项:
|
||||
echo - 程序支持智能文件识别
|
||||
echo - 支持1-10人连坐需求
|
||||
echo - 支持备注分组和手机号分组两种模式
|
||||
echo.
|
||||
echo 如有问题请查看日志文件
|
||||
) > "release\使用说明.txt"
|
||||
|
||||
echo.
|
||||
echo ================================
|
||||
echo 打包完成!
|
||||
echo ================================
|
||||
echo.
|
||||
echo 发布文件位置: release\
|
||||
echo 主程序: release\座位分配系统.exe
|
||||
echo 使用说明: release\使用说明.txt
|
||||
echo 示例文件: release\示例文件\
|
||||
echo.
|
||||
echo 您可以将整个release文件夹分发给用户
|
||||
|
||||
) else (
|
||||
echo.
|
||||
echo [错误] 打包失败!
|
||||
echo 请检查错误信息并重试
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo.
|
||||
echo 按任意键继续...
|
||||
pause >nul
|
226
build_windows.ps1
Normal file
226
build_windows.ps1
Normal file
@@ -0,0 +1,226 @@
|
||||
# 座位分配系统 Windows 打包脚本 (PowerShell版本)
|
||||
# 编码:UTF-8
|
||||
|
||||
Write-Host "================================" -ForegroundColor Cyan
|
||||
Write-Host " 座位分配系统 Windows 打包脚本" -ForegroundColor Cyan
|
||||
Write-Host "================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# 设置错误处理
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
try {
|
||||
# 检查Python是否安装
|
||||
Write-Host "[信息] 检查Python环境..." -ForegroundColor Yellow
|
||||
$pythonVersion = python --version 2>&1
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Python未安装或不在PATH中,请先安装Python 3.7+"
|
||||
}
|
||||
Write-Host "Python版本: $pythonVersion" -ForegroundColor Green
|
||||
|
||||
# 检查pip
|
||||
$pipVersion = pip --version 2>&1
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "pip不可用"
|
||||
}
|
||||
Write-Host "pip版本: $pipVersion" -ForegroundColor Green
|
||||
|
||||
# 升级pip
|
||||
Write-Host ""
|
||||
Write-Host "[步骤1] 升级pip..." -ForegroundColor Yellow
|
||||
python -m pip install --upgrade pip
|
||||
|
||||
# 安装PyInstaller
|
||||
Write-Host ""
|
||||
Write-Host "[步骤2] 安装PyInstaller..." -ForegroundColor Yellow
|
||||
pip install pyinstaller
|
||||
|
||||
# 安装依赖包
|
||||
Write-Host ""
|
||||
Write-Host "[步骤3] 安装依赖包..." -ForegroundColor Yellow
|
||||
pip install pandas numpy openpyxl
|
||||
|
||||
# 创建必要的目录
|
||||
Write-Host ""
|
||||
Write-Host "[步骤4] 创建构建目录..." -ForegroundColor Yellow
|
||||
$dirs = @("build", "dist", "release", "release\示例文件")
|
||||
foreach ($dir in $dirs) {
|
||||
if (!(Test-Path $dir)) {
|
||||
New-Item -ItemType Directory -Path $dir -Force | Out-Null
|
||||
Write-Host "创建目录: $dir" -ForegroundColor Gray
|
||||
}
|
||||
}
|
||||
|
||||
# 检查主文件是否存在
|
||||
if (!(Test-Path "seat_allocation_system.py")) {
|
||||
throw "找不到主程序文件 seat_allocation_system.py"
|
||||
}
|
||||
|
||||
# 打包主程序
|
||||
Write-Host ""
|
||||
Write-Host "[步骤5] 开始打包座位分配系统..." -ForegroundColor Yellow
|
||||
Write-Host "这可能需要几分钟时间,请耐心等待..." -ForegroundColor Gray
|
||||
|
||||
$pyinstallerArgs = @(
|
||||
"--onefile",
|
||||
"--console",
|
||||
"--name", "座位分配系统",
|
||||
"--distpath", "dist",
|
||||
"--workpath", "build",
|
||||
"--specpath", "build",
|
||||
"--clean",
|
||||
"seat_allocation_system.py"
|
||||
)
|
||||
|
||||
# 如果存在图标文件,添加图标参数
|
||||
if (Test-Path "icon.ico") {
|
||||
$pyinstallerArgs += "--icon", "icon.ico"
|
||||
}
|
||||
|
||||
# 如果存在README文件,添加数据文件
|
||||
if (Test-Path "README.md") {
|
||||
$pyinstallerArgs += "--add-data", "README.md;."
|
||||
}
|
||||
|
||||
& pyinstaller @pyinstallerArgs
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "PyInstaller打包失败"
|
||||
}
|
||||
|
||||
# 检查打包结果
|
||||
$exePath = "dist\座位分配系统.exe"
|
||||
if (!(Test-Path $exePath)) {
|
||||
throw "打包失败:找不到生成的可执行文件"
|
||||
}
|
||||
|
||||
# 复制文件到发布目录
|
||||
Write-Host ""
|
||||
Write-Host "[步骤6] 创建发布包..." -ForegroundColor Yellow
|
||||
|
||||
# 复制主程序
|
||||
Copy-Item $exePath "release\" -Force
|
||||
Write-Host "复制主程序到发布目录" -ForegroundColor Gray
|
||||
|
||||
# 复制示例文件
|
||||
$exampleFiles = @("人员信息_示例.xlsx", "座位信息_示例.xlsx", "*.xlsx")
|
||||
foreach ($pattern in $exampleFiles) {
|
||||
$files = Get-ChildItem -Path . -Name $pattern -ErrorAction SilentlyContinue
|
||||
foreach ($file in $files) {
|
||||
if ($file -notlike "*_最终分配*" -and $file -notlike "*日志*") {
|
||||
Copy-Item $file "release\示例文件\" -Force -ErrorAction SilentlyContinue
|
||||
Write-Host "复制示例文件: $file" -ForegroundColor Gray
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# 创建使用说明
|
||||
Write-Host ""
|
||||
Write-Host "[步骤7] 创建使用说明..." -ForegroundColor Yellow
|
||||
|
||||
$readme = @"
|
||||
座位分配系统 v2.0
|
||||
==================
|
||||
|
||||
使用方法:
|
||||
1. 将人员信息和座位信息的Excel文件放在程序同一目录下
|
||||
2. 双击运行"座位分配系统.exe"
|
||||
3. 按照提示操作
|
||||
|
||||
文件要求:
|
||||
- 人员信息文件:包含姓名、证件类型、证件号、手机号、备注等列
|
||||
- 座位信息文件:包含区域、楼层、排号、座位号等列
|
||||
|
||||
程序特性:
|
||||
- ✅ 智能文件识别(自动识别Excel文件类型)
|
||||
- ✅ 支持1-10人连坐需求
|
||||
- ✅ 支持备注分组和手机号分组两种模式
|
||||
- ✅ 自动验证数据完整性和可行性
|
||||
- ✅ 智能座位分配算法
|
||||
- ✅ 详细的分配日志和统计
|
||||
|
||||
输出文件:
|
||||
程序运行后会在当前目录创建log文件夹,包含:
|
||||
- log/座位信息_最终分配.xlsx (分配结果)
|
||||
- log/最终座位分配日志.xlsx (详细日志)
|
||||
- log/seat_allocation_log.txt (运行日志)
|
||||
|
||||
操作步骤:
|
||||
1. 选择文件识别模式(默认文件名 或 智能识别)
|
||||
2. 选择连坐分组方式(备注分组 或 手机号分组)
|
||||
3. 程序自动校验数据完整性
|
||||
4. 执行智能座位分配
|
||||
5. 查看输出结果
|
||||
|
||||
注意事项:
|
||||
- 确保Excel文件格式正确
|
||||
- 人员信息文件的"备注"列用数字表示连坐人数
|
||||
- 程序会自动过滤无效数据
|
||||
- 如有问题请查看详细日志文件
|
||||
|
||||
技术支持:
|
||||
如遇到问题,请查看log文件夹中的日志文件获取详细信息。
|
||||
|
||||
版本信息:
|
||||
- 版本:v2.0 (智能识别版)
|
||||
- 支持:Windows 7/8/10/11
|
||||
- 依赖:无需额外安装Python环境
|
||||
"@
|
||||
|
||||
$readme | Out-File -FilePath "release\使用说明.txt" -Encoding UTF8
|
||||
|
||||
# 创建批处理启动文件(可选)
|
||||
$batchContent = @"
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
echo 启动座位分配系统...
|
||||
echo.
|
||||
座位分配系统.exe
|
||||
echo.
|
||||
echo 程序已结束
|
||||
pause
|
||||
"@
|
||||
$batchContent | Out-File -FilePath "release\启动座位分配系统.bat" -Encoding UTF8
|
||||
|
||||
# 显示完成信息
|
||||
Write-Host ""
|
||||
Write-Host "================================" -ForegroundColor Green
|
||||
Write-Host " 打包完成!" -ForegroundColor Green
|
||||
Write-Host "================================" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "发布文件位置:" -ForegroundColor Cyan
|
||||
Write-Host "📁 release\" -ForegroundColor Yellow
|
||||
Write-Host " ├── 座位分配系统.exe (主程序)" -ForegroundColor White
|
||||
Write-Host " ├── 启动座位分配系统.bat (启动脚本)" -ForegroundColor White
|
||||
Write-Host " ├── 使用说明.txt (使用说明)" -ForegroundColor White
|
||||
Write-Host " └── 示例文件\ (示例文件夹)" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host "文件大小信息:" -ForegroundColor Cyan
|
||||
$fileSize = (Get-Item $exePath).Length / 1MB
|
||||
Write-Host "主程序大小: $([math]::Round($fileSize, 2)) MB" -ForegroundColor Yellow
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "部署说明:" -ForegroundColor Cyan
|
||||
Write-Host "1. 将整个 release 文件夹分发给用户" -ForegroundColor White
|
||||
Write-Host "2. 用户直接运行 座位分配系统.exe 或 启动座位分配系统.bat" -ForegroundColor White
|
||||
Write-Host "3. 无需安装Python环境" -ForegroundColor White
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "✅ 打包成功完成!" -ForegroundColor Green
|
||||
|
||||
} catch {
|
||||
Write-Host ""
|
||||
Write-Host "❌ 打包过程中出现错误:" -ForegroundColor Red
|
||||
Write-Host $_.Exception.Message -ForegroundColor Red
|
||||
Write-Host ""
|
||||
Write-Host "常见解决方案:" -ForegroundColor Yellow
|
||||
Write-Host "1. 确保已安装Python 3.7+" -ForegroundColor White
|
||||
Write-Host "2. 确保网络连接正常(需要下载依赖包)" -ForegroundColor White
|
||||
Write-Host "3. 确保有足够的磁盘空间" -ForegroundColor White
|
||||
Write-Host "4. 尝试以管理员权限运行此脚本" -ForegroundColor White
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "按任意键继续..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
@@ -1,326 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
跨平台构建脚本
|
||||
在macOS上构建Windows x86/x64版本的exe文件
|
||||
使用Wine和PyInstaller实现跨平台编译
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import platform
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
def check_dependencies():
|
||||
"""检查必要的依赖"""
|
||||
print("检查构建依赖...")
|
||||
|
||||
# 检查是否在macOS上
|
||||
if platform.system() != 'Darwin':
|
||||
print(f"当前系统: {platform.system()}")
|
||||
print("此脚本专为macOS设计,用于构建Windows版本")
|
||||
return False
|
||||
|
||||
# 检查Python包
|
||||
required_packages = ['pandas', 'openpyxl', 'pyinstaller']
|
||||
missing_packages = []
|
||||
|
||||
for package in required_packages:
|
||||
try:
|
||||
__import__(package)
|
||||
print(f"✅ {package} 已安装")
|
||||
except ImportError:
|
||||
missing_packages.append(package)
|
||||
print(f"❌ {package} 未安装")
|
||||
|
||||
if missing_packages:
|
||||
print(f"\n安装缺失的包...")
|
||||
for package in missing_packages:
|
||||
try:
|
||||
subprocess.check_call([sys.executable, '-m', 'pip', 'install', package])
|
||||
print(f"✅ {package} 安装成功")
|
||||
except subprocess.CalledProcessError:
|
||||
print(f"❌ {package} 安装失败")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def install_wine():
|
||||
"""安装Wine(如果需要)"""
|
||||
print("\n检查Wine环境...")
|
||||
|
||||
# 检查是否已安装Wine
|
||||
try:
|
||||
result = subprocess.run(['wine', '--version'], capture_output=True, text=True)
|
||||
if result.returncode == 0:
|
||||
print(f"✅ Wine已安装: {result.stdout.strip()}")
|
||||
return True
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
print("Wine未安装,开始安装...")
|
||||
print("请按照以下步骤手动安装Wine:")
|
||||
print("1. 安装Homebrew (如果未安装): /bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"")
|
||||
print("2. 安装Wine: brew install --cask wine-stable")
|
||||
print("3. 配置Wine: winecfg")
|
||||
|
||||
response = input("是否已完成Wine安装? (y/N): ")
|
||||
return response.lower() == 'y'
|
||||
|
||||
def setup_wine_python():
|
||||
"""在Wine中设置Python环境"""
|
||||
print("\n设置Wine Python环境...")
|
||||
|
||||
# 检查Wine中是否已安装Python
|
||||
try:
|
||||
result = subprocess.run(['wine', 'python', '--version'], capture_output=True, text=True)
|
||||
if result.returncode == 0:
|
||||
print(f"✅ Wine Python已安装: {result.stdout.strip()}")
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
|
||||
print("需要在Wine中安装Python...")
|
||||
print("请按照以下步骤:")
|
||||
print("1. 下载Windows版Python: https://www.python.org/downloads/windows/")
|
||||
print("2. 使用Wine安装: wine python-3.10.x-amd64.exe")
|
||||
print("3. 安装pip包: wine python -m pip install pandas openpyxl pyinstaller")
|
||||
|
||||
response = input("是否已完成Wine Python安装? (y/N): ")
|
||||
return response.lower() == 'y'
|
||||
|
||||
def create_build_spec(target_arch='x64'):
|
||||
"""创建构建规格文件"""
|
||||
exe_name = f"座位分配系统_{target_arch}"
|
||||
|
||||
spec_content = f'''# -*- 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'
|
||||
],
|
||||
hookspath=[],
|
||||
hooksconfig={{}},
|
||||
runtime_hooks=[],
|
||||
excludes=[
|
||||
'matplotlib',
|
||||
'scipy',
|
||||
'IPython',
|
||||
'jupyter',
|
||||
'notebook',
|
||||
'tkinter'
|
||||
],
|
||||
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='{exe_name}',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=True,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
icon=None,
|
||||
)
|
||||
'''
|
||||
|
||||
spec_file = f'{exe_name}.spec'
|
||||
with open(spec_file, 'w', encoding='utf-8') as f:
|
||||
f.write(spec_content)
|
||||
|
||||
return spec_file, exe_name
|
||||
|
||||
def build_with_docker():
|
||||
"""使用Docker构建Windows版本"""
|
||||
print("\n使用Docker构建Windows版本...")
|
||||
|
||||
# 创建Dockerfile
|
||||
dockerfile_content = '''FROM python:3.10-windowsservercore
|
||||
|
||||
# 设置工作目录
|
||||
WORKDIR /app
|
||||
|
||||
# 复制文件
|
||||
COPY requirements.txt .
|
||||
COPY seat_allocation_system.py .
|
||||
|
||||
# 安装依赖
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
# 构建exe
|
||||
RUN pyinstaller --onefile --console --name "座位分配系统_x64" seat_allocation_system.py
|
||||
|
||||
# 输出目录
|
||||
VOLUME ["/app/dist"]
|
||||
'''
|
||||
|
||||
with open('Dockerfile.windows', 'w') as f:
|
||||
f.write(dockerfile_content)
|
||||
|
||||
print("Docker方法需要Docker Desktop和Windows容器支持")
|
||||
print("这是一个高级选项,建议使用其他方法")
|
||||
return False
|
||||
|
||||
def build_native_macos():
|
||||
"""在macOS上直接构建(生成macOS版本)"""
|
||||
print("\n在macOS上构建本地版本...")
|
||||
|
||||
# 清理之前的构建
|
||||
if os.path.exists('dist'):
|
||||
shutil.rmtree('dist')
|
||||
if os.path.exists('build'):
|
||||
shutil.rmtree('build')
|
||||
|
||||
# 构建命令
|
||||
cmd = [
|
||||
sys.executable, '-m', 'PyInstaller',
|
||||
'--onefile',
|
||||
'--console',
|
||||
'--clean',
|
||||
'--name', '座位分配系统_macos',
|
||||
'seat_allocation_system.py'
|
||||
]
|
||||
|
||||
try:
|
||||
print(f"执行命令: {' '.join(cmd)}")
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
|
||||
if result.returncode == 0:
|
||||
print("✅ macOS版本构建成功!")
|
||||
|
||||
# 检查生成的文件
|
||||
app_path = Path('dist') / '座位分配系统_macos'
|
||||
if app_path.exists():
|
||||
file_size = app_path.stat().st_size / (1024 * 1024) # MB
|
||||
print(f"生成文件: {app_path}")
|
||||
print(f"文件大小: {file_size:.1f} MB")
|
||||
return True, app_path
|
||||
else:
|
||||
print("❌ 构建失败!")
|
||||
print("错误输出:")
|
||||
print(result.stderr)
|
||||
return False, None
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 构建过程中出现错误: {e}")
|
||||
return False, None
|
||||
|
||||
def create_cross_compile_script():
|
||||
"""创建跨编译脚本"""
|
||||
script_content = '''#!/bin/bash
|
||||
# Windows交叉编译脚本
|
||||
|
||||
echo "座位分配系统 - Windows交叉编译"
|
||||
echo "================================"
|
||||
|
||||
# 检查是否安装了mingw-w64
|
||||
if ! command -v x86_64-w64-mingw32-gcc &> /dev/null; then
|
||||
echo "安装mingw-w64..."
|
||||
brew install mingw-w64
|
||||
fi
|
||||
|
||||
# 设置交叉编译环境
|
||||
export CC=x86_64-w64-mingw32-gcc
|
||||
export CXX=x86_64-w64-mingw32-g++
|
||||
|
||||
# 构建Windows版本
|
||||
echo "开始构建Windows x64版本..."
|
||||
python -m PyInstaller \\
|
||||
--onefile \\
|
||||
--console \\
|
||||
--clean \\
|
||||
--name "座位分配系统_x64" \\
|
||||
--target-arch x86_64 \\
|
||||
seat_allocation_system.py
|
||||
|
||||
echo "构建完成!"
|
||||
'''
|
||||
|
||||
with open('cross_compile.sh', 'w') as f:
|
||||
f.write(script_content)
|
||||
|
||||
# 设置执行权限
|
||||
os.chmod('cross_compile.sh', 0o755)
|
||||
print("✅ 已创建交叉编译脚本: cross_compile.sh")
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("座位分配系统 - 跨平台构建工具")
|
||||
print("=" * 50)
|
||||
print("在macOS上构建Windows版本")
|
||||
|
||||
# 检查依赖
|
||||
if not check_dependencies():
|
||||
return
|
||||
|
||||
print("\n选择构建方法:")
|
||||
print("1. 构建macOS版本(推荐)")
|
||||
print("2. 使用Wine构建Windows版本(复杂)")
|
||||
print("3. 创建交叉编译脚本(实验性)")
|
||||
print("4. 生成Docker构建文件(高级)")
|
||||
|
||||
choice = input("\n请选择 (1-4): ").strip()
|
||||
|
||||
if choice == '1':
|
||||
print("\n构建macOS版本...")
|
||||
success, app_path = build_native_macos()
|
||||
if success:
|
||||
print(f"\n🎉 构建完成!")
|
||||
print(f"生成文件: {app_path}")
|
||||
print("\n注意: 这是macOS版本,不能在Windows上运行")
|
||||
print("如需Windows版本,请在Windows系统上运行构建脚本")
|
||||
|
||||
elif choice == '2':
|
||||
print("\n使用Wine构建Windows版本...")
|
||||
if install_wine() and setup_wine_python():
|
||||
print("Wine环境已准备就绪")
|
||||
print("请手动在Wine中运行: wine python windows_build.py")
|
||||
else:
|
||||
print("Wine环境设置失败")
|
||||
|
||||
elif choice == '3':
|
||||
print("\n创建交叉编译脚本...")
|
||||
create_cross_compile_script()
|
||||
print("请运行: ./cross_compile.sh")
|
||||
|
||||
elif choice == '4':
|
||||
print("\n生成Docker构建文件...")
|
||||
build_with_docker()
|
||||
|
||||
else:
|
||||
print("无效选择")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
42
fix_dll_build.bat
Normal file
42
fix_dll_build.bat
Normal file
@@ -0,0 +1,42 @@
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
echo ============================================================
|
||||
echo pandas DLL修复构建脚本
|
||||
echo 专门解决: ImportError: DLL load failed while importing aggregations
|
||||
echo ============================================================
|
||||
echo.
|
||||
|
||||
echo 检查Python环境...
|
||||
python --version
|
||||
if errorlevel 1 (
|
||||
echo ❌ Python未安装或未添加到PATH
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo.
|
||||
echo 检查pandas安装...
|
||||
python -c "import pandas; print(f'✅ pandas版本: {pandas.__version__}')"
|
||||
if errorlevel 1 (
|
||||
echo ❌ pandas未安装
|
||||
echo 正在安装pandas...
|
||||
pip install pandas
|
||||
)
|
||||
|
||||
echo.
|
||||
echo 检查PyInstaller...
|
||||
python -c "import PyInstaller; print('✅ PyInstaller已安装')"
|
||||
if errorlevel 1 (
|
||||
echo ❌ PyInstaller未安装
|
||||
echo 正在安装PyInstaller...
|
||||
pip install pyinstaller
|
||||
)
|
||||
|
||||
echo.
|
||||
echo 开始DLL修复构建...
|
||||
python fix_dll_build.py
|
||||
|
||||
echo.
|
||||
echo 构建完成!
|
||||
echo 如果成功,可执行文件位于: dist\座位分配系统_修复版.exe
|
||||
pause
|
302
fix_dll_build.py
Normal file
302
fix_dll_build.py
Normal file
@@ -0,0 +1,302 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
修复DLL加载失败的专用构建脚本
|
||||
专门解决pandas._libs.window.aggregations DLL问题
|
||||
"""
|
||||
|
||||
import sys
|
||||
import subprocess
|
||||
import platform
|
||||
import site
|
||||
from pathlib import Path
|
||||
|
||||
def get_pandas_libs_path():
|
||||
"""获取pandas库文件路径"""
|
||||
try:
|
||||
import pandas
|
||||
pandas_path = Path(pandas.__file__).parent
|
||||
libs_path = pandas_path / '_libs'
|
||||
return libs_path
|
||||
except ImportError:
|
||||
return None
|
||||
|
||||
def create_dll_fix_spec():
|
||||
"""创建修复DLL问题的spec文件"""
|
||||
|
||||
# 获取pandas库路径
|
||||
pandas_libs = get_pandas_libs_path()
|
||||
if not pandas_libs or not pandas_libs.exists():
|
||||
print("❌ 无法找到pandas库路径")
|
||||
return None
|
||||
|
||||
print(f"✅ 找到pandas库路径: {pandas_libs}")
|
||||
|
||||
# 查找所有.pyd文件(Windows DLL)
|
||||
pyd_files = list(pandas_libs.glob('**/*.pyd'))
|
||||
print(f"✅ 找到 {len(pyd_files)} 个pandas DLL文件")
|
||||
|
||||
# 构建binaries列表
|
||||
binaries_list = []
|
||||
for pyd_file in pyd_files:
|
||||
rel_path = pyd_file.relative_to(pandas_libs.parent)
|
||||
binaries_list.append(f"(r'{pyd_file}', r'{rel_path.parent}')")
|
||||
|
||||
binaries_str = ',\n '.join(binaries_list)
|
||||
|
||||
spec_content = f'''# -*- mode: python ; coding: utf-8 -*-
|
||||
# 专门修复pandas DLL加载问题的配置
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
block_cipher = None
|
||||
|
||||
a = Analysis(
|
||||
['seat_allocation_system.py'],
|
||||
pathex=[],
|
||||
binaries=[
|
||||
# 手动包含所有pandas DLL文件
|
||||
{binaries_str}
|
||||
],
|
||||
datas=[],
|
||||
hiddenimports=[
|
||||
# 核心模块
|
||||
'pandas',
|
||||
'numpy',
|
||||
'openpyxl',
|
||||
|
||||
# pandas核心库(必须)
|
||||
'pandas._libs',
|
||||
'pandas._libs.lib',
|
||||
'pandas._libs.hashtable',
|
||||
'pandas._libs.tslib',
|
||||
'pandas._libs.algos',
|
||||
'pandas._libs.join',
|
||||
'pandas._libs.index',
|
||||
'pandas._libs.internals',
|
||||
'pandas._libs.writers',
|
||||
'pandas._libs.parsers',
|
||||
'pandas._libs.testing',
|
||||
'pandas._libs.sparse',
|
||||
'pandas._libs.reduction',
|
||||
'pandas._libs.ops',
|
||||
'pandas._libs.missing',
|
||||
'pandas._libs.groupby',
|
||||
'pandas._libs.reshape',
|
||||
|
||||
# pandas时间序列库
|
||||
'pandas._libs.tslibs',
|
||||
'pandas._libs.tslibs.base',
|
||||
'pandas._libs.tslibs.ccalendar',
|
||||
'pandas._libs.tslibs.conversion',
|
||||
'pandas._libs.tslibs.dtypes',
|
||||
'pandas._libs.tslibs.fields',
|
||||
'pandas._libs.tslibs.nattype',
|
||||
'pandas._libs.tslibs.np_datetime',
|
||||
'pandas._libs.tslibs.offsets',
|
||||
'pandas._libs.tslibs.parsing',
|
||||
'pandas._libs.tslibs.period',
|
||||
'pandas._libs.tslibs.strptime',
|
||||
'pandas._libs.tslibs.timedeltas',
|
||||
'pandas._libs.tslibs.timestamps',
|
||||
'pandas._libs.tslibs.timezones',
|
||||
'pandas._libs.tslibs.tzconversion',
|
||||
'pandas._libs.tslibs.vectorized',
|
||||
|
||||
# pandas窗口函数库(关键!)
|
||||
'pandas._libs.window',
|
||||
'pandas._libs.window.aggregations',
|
||||
'pandas._libs.window.indexers',
|
||||
|
||||
# numpy核心
|
||||
'numpy.core._methods',
|
||||
'numpy.core._dtype_ctypes',
|
||||
'numpy.core.multiarray',
|
||||
'numpy.core.umath',
|
||||
|
||||
# 其他必要模块
|
||||
'ctypes.util',
|
||||
'pkg_resources.py2_warn',
|
||||
'encodings.utf_8',
|
||||
'encodings.gbk',
|
||||
|
||||
# openpyxl
|
||||
'openpyxl.workbook',
|
||||
'openpyxl.worksheet',
|
||||
'openpyxl.styles',
|
||||
],
|
||||
hookspath=[],
|
||||
hooksconfig={{}},
|
||||
runtime_hooks=[],
|
||||
excludes=[
|
||||
'matplotlib',
|
||||
'scipy',
|
||||
'IPython',
|
||||
'jupyter',
|
||||
'tkinter',
|
||||
'PyQt5',
|
||||
'PyQt6',
|
||||
'PIL',
|
||||
'cv2',
|
||||
'sklearn',
|
||||
'tensorflow',
|
||||
'torch',
|
||||
],
|
||||
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压缩,避免DLL问题
|
||||
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 = Path('dll_fix.spec')
|
||||
with open(spec_file, 'w', encoding='utf-8') as f:
|
||||
f.write(spec_content)
|
||||
|
||||
print(f"✅ 创建DLL修复spec文件: {spec_file}")
|
||||
return spec_file
|
||||
|
||||
def build_with_dll_fix():
|
||||
"""使用DLL修复配置构建"""
|
||||
print("=" * 60)
|
||||
print("DLL修复构建")
|
||||
print("=" * 60)
|
||||
|
||||
# 检查环境
|
||||
if platform.system() != 'Windows':
|
||||
print("❌ 此脚本仅适用于Windows")
|
||||
return False
|
||||
|
||||
print(f"✅ 系统: {platform.system()} {platform.release()}")
|
||||
|
||||
# 检查主文件
|
||||
main_file = Path('seat_allocation_system.py')
|
||||
if not main_file.exists():
|
||||
print("❌ 未找到主程序文件")
|
||||
return False
|
||||
|
||||
# 创建修复spec文件
|
||||
spec_file = create_dll_fix_spec()
|
||||
if not spec_file:
|
||||
return False
|
||||
|
||||
# 清理旧文件
|
||||
dist_dir = Path('dist')
|
||||
if dist_dir.exists():
|
||||
import shutil
|
||||
shutil.rmtree(dist_dir)
|
||||
print("✅ 清理dist目录")
|
||||
|
||||
# 构建命令
|
||||
cmd = [
|
||||
sys.executable, '-m', 'PyInstaller',
|
||||
'--clean',
|
||||
'--noconfirm',
|
||||
'--log-level=WARN', # 减少输出
|
||||
str(spec_file)
|
||||
]
|
||||
|
||||
print(f"\n执行命令: {' '.join(cmd)}")
|
||||
print("开始DLL修复构建...")
|
||||
|
||||
try:
|
||||
# 执行构建
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding='utf-8',
|
||||
errors='ignore'
|
||||
)
|
||||
|
||||
# 显示结果
|
||||
if result.returncode == 0:
|
||||
print("✅ DLL修复构建成功!")
|
||||
|
||||
# 检查生成的文件
|
||||
exe_path = Path('dist/座位分配系统_修复版.exe')
|
||||
if exe_path.exists():
|
||||
size_mb = exe_path.stat().st_size / (1024 * 1024)
|
||||
print(f"✅ 生成文件: {exe_path}")
|
||||
print(f"✅ 文件大小: {size_mb:.1f} MB")
|
||||
|
||||
# 测试运行
|
||||
print("\n🧪 测试运行...")
|
||||
test_cmd = [str(exe_path), '--help']
|
||||
try:
|
||||
test_result = subprocess.run(
|
||||
test_cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10
|
||||
)
|
||||
if test_result.returncode == 0:
|
||||
print("✅ 程序可以正常启动")
|
||||
else:
|
||||
print("⚠️ 程序启动有问题,但文件已生成")
|
||||
except subprocess.TimeoutExpired:
|
||||
print("⚠️ 测试超时,但文件已生成")
|
||||
except Exception as e:
|
||||
print(f"⚠️ 测试失败: {e}")
|
||||
|
||||
return True
|
||||
else:
|
||||
print("❌ 未找到生成的exe文件")
|
||||
return False
|
||||
else:
|
||||
print("❌ DLL修复构建失败!")
|
||||
print("错误输出:")
|
||||
print(result.stderr)
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 构建出错: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("🔧 pandas DLL加载失败修复工具")
|
||||
print("专门解决: ImportError: DLL load failed while importing aggregations")
|
||||
print()
|
||||
|
||||
success = build_with_dll_fix()
|
||||
|
||||
if success:
|
||||
print("\n🎉 DLL修复构建完成!")
|
||||
print("可执行文件: dist/座位分配系统_修复版.exe")
|
||||
print("\n💡 如果仍有问题,请尝试:")
|
||||
print("1. 在目标机器上安装 Visual C++ Redistributable")
|
||||
print("2. 确保目标机器有相同的Windows版本")
|
||||
else:
|
||||
print("\n❌ DLL修复构建失败!")
|
||||
print("请检查pandas安装是否完整")
|
||||
|
||||
input("\n按Enter键退出...")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@@ -6,16 +6,209 @@
|
||||
支持1-10人连坐需求,能够处理不连续座位
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
import datetime
|
||||
import sys
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
|
||||
def check_dependencies():
|
||||
"""检查并安装必要的依赖包"""
|
||||
required_packages = {
|
||||
'pandas': 'pandas>=1.3.0',
|
||||
'numpy': 'numpy>=1.20.0',
|
||||
'openpyxl': 'openpyxl>=3.0.0'
|
||||
}
|
||||
|
||||
missing_packages = []
|
||||
|
||||
print("检查依赖包...")
|
||||
for package_name, package_spec in required_packages.items():
|
||||
try:
|
||||
__import__(package_name)
|
||||
print(f"✅ {package_name} 已安装")
|
||||
except ImportError:
|
||||
missing_packages.append(package_spec)
|
||||
print(f"❌ {package_name} 未安装")
|
||||
|
||||
if missing_packages:
|
||||
print(f"\n发现缺失依赖: {', '.join(missing_packages)}")
|
||||
print("正在尝试自动安装...")
|
||||
|
||||
try:
|
||||
import subprocess
|
||||
for package in missing_packages:
|
||||
print(f"安装 {package}...")
|
||||
result = subprocess.run([sys.executable, '-m', 'pip', 'install', package],
|
||||
capture_output=True, text=True)
|
||||
if result.returncode == 0:
|
||||
print(f"✅ {package} 安装成功")
|
||||
else:
|
||||
print(f"❌ {package} 安装失败: {result.stderr}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"❌ 自动安装失败: {e}")
|
||||
print("\n请手动安装以下依赖包:")
|
||||
for package in missing_packages:
|
||||
print(f" pip install {package}")
|
||||
input("安装完成后按Enter键继续...")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def auto_detect_files():
|
||||
"""自动检测和识别Excel文件"""
|
||||
print("\n=== 自动文件识别系统 ===")
|
||||
|
||||
# 扫描当前目录下的所有xlsx文件
|
||||
current_dir = Path('.')
|
||||
xlsx_files = list(current_dir.glob('*.xlsx'))
|
||||
|
||||
# 过滤掉输出文件和示例文件
|
||||
exclude_patterns = ['座位信息_最终分配', '最终座位分配日志', '_示例', '_temp', '_backup']
|
||||
xlsx_files = [f for f in xlsx_files if not any(pattern in f.name for pattern in exclude_patterns)]
|
||||
|
||||
print(f"发现 {len(xlsx_files)} 个Excel文件:")
|
||||
for i, file_path in enumerate(xlsx_files, 1):
|
||||
file_size = file_path.stat().st_size / 1024 # KB
|
||||
print(f" {i}. {file_path.name} ({file_size:.1f} KB)")
|
||||
|
||||
if len(xlsx_files) == 0:
|
||||
print("\n❌ 未找到Excel文件")
|
||||
print("请确保当前目录下有Excel数据文件")
|
||||
return None, None
|
||||
|
||||
if len(xlsx_files) > 2:
|
||||
print(f"\n⚠️ 发现 {len(xlsx_files)} 个Excel文件,超过2个")
|
||||
print("为避免混淆,请确保目录下只有2个数据文件")
|
||||
print("(程序会自动忽略输出文件和示例文件)")
|
||||
|
||||
print("\n请手动移除多余文件,或选择要使用的文件:")
|
||||
for i, file_path in enumerate(xlsx_files, 1):
|
||||
print(f" {i}. {file_path.name}")
|
||||
|
||||
return None, None
|
||||
|
||||
# 分析文件结构
|
||||
print(f"\n正在分析文件结构...")
|
||||
file_info = []
|
||||
|
||||
for file_path in xlsx_files:
|
||||
try:
|
||||
# 读取文件获取基本信息
|
||||
df = pd.read_excel(file_path, nrows=5) # 只读前5行进行分析
|
||||
|
||||
info = {
|
||||
'path': file_path,
|
||||
'name': file_path.name,
|
||||
'columns': len(df.columns),
|
||||
'rows': len(df) + 1, # 加上标题行
|
||||
'column_names': list(df.columns),
|
||||
'sample_data': df.head(2) # 获取前2行样本数据
|
||||
}
|
||||
file_info.append(info)
|
||||
|
||||
print(f"\n📄 {file_path.name}:")
|
||||
print(f" 列数: {info['columns']}")
|
||||
print(f" 行数: {info['rows']}+ (仅检查前5行)")
|
||||
print(f" 列名: {', '.join(str(col) for col in info['column_names'][:6])}{'...' if len(info['column_names']) > 6 else ''}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ 读取文件 {file_path.name} 失败: {e}")
|
||||
return None, None
|
||||
|
||||
# 根据列数自动识别文件类型
|
||||
personnel_file = None
|
||||
seat_file = None
|
||||
|
||||
print(f"\n=== 文件类型识别 ===")
|
||||
|
||||
for info in file_info:
|
||||
if info['columns'] == 5 or info['columns'] == 6: # 人员信息通常是5-6列
|
||||
# 进一步检查列名是否符合人员信息格式
|
||||
col_names = [str(col).lower() for col in info['column_names']]
|
||||
personnel_keywords = ['姓名', 'name', '证件', 'id', '手机', 'phone', '备注', 'remark']
|
||||
|
||||
if any(keyword in ' '.join(col_names) for keyword in personnel_keywords):
|
||||
personnel_file = info
|
||||
print(f"✅ {info['name']} -> 识别为人员信息文件 ({info['columns']}列)")
|
||||
else:
|
||||
print(f"⚠️ {info['name']} 有{info['columns']}列,但列名不符合人员信息格式")
|
||||
|
||||
elif info['columns'] >= 10: # 座位信息通常是10-12列
|
||||
# 进一步检查列名是否符合座位信息格式
|
||||
col_names = [str(col).lower() for col in info['column_names']]
|
||||
seat_keywords = ['区域', 'area', '楼层', 'floor', '排号', 'row', '座位', 'seat']
|
||||
|
||||
if any(keyword in ' '.join(col_names) for keyword in seat_keywords):
|
||||
seat_file = info
|
||||
print(f"✅ {info['name']} -> 识别为座位信息文件 ({info['columns']}列)")
|
||||
else:
|
||||
print(f"⚠️ {info['name']} 有{info['columns']}列,但列名不符合座位信息格式")
|
||||
else:
|
||||
print(f"❓ {info['name']} 有{info['columns']}列,无法自动识别类型")
|
||||
|
||||
# 验证识别结果
|
||||
if personnel_file is None:
|
||||
print(f"\n❌ 未找到人员信息文件")
|
||||
print("人员信息文件应包含: 姓名、证件类型、证件号、手机号、备注等列")
|
||||
return None, None
|
||||
|
||||
if seat_file is None:
|
||||
print(f"\n❌ 未找到座位信息文件")
|
||||
print("座位信息文件应包含: 区域、楼层、排号、座位号等列")
|
||||
return None, None
|
||||
|
||||
print(f"\n🎉 文件识别成功!")
|
||||
print(f"人员信息: {personnel_file['name']}")
|
||||
print(f"座位信息: {seat_file['name']}")
|
||||
|
||||
# 显示文件内容预览
|
||||
print(f"\n=== 文件内容预览 ===")
|
||||
|
||||
print(f"\n📋 人员信息文件预览 ({personnel_file['name']}):")
|
||||
print(personnel_file['sample_data'].to_string(index=False, max_cols=10))
|
||||
|
||||
print(f"\n🪑 座位信息文件预览 ({seat_file['name']}):")
|
||||
print(seat_file['sample_data'].to_string(index=False, max_cols=10))
|
||||
|
||||
return personnel_file['path'], seat_file['path']
|
||||
|
||||
def check_data_files():
|
||||
"""检查并自动识别数据文件"""
|
||||
print("\n=== 数据文件检查 ===")
|
||||
|
||||
# 首先尝试自动识别
|
||||
personnel_file, seat_file = auto_detect_files()
|
||||
|
||||
if personnel_file is None or seat_file is None:
|
||||
print(f"\n❌ 自动识别失败")
|
||||
print("\n请检查以下要求:")
|
||||
print("1. 确保目录下有且仅有2个Excel文件")
|
||||
print("2. 人员信息文件应有5-6列(姓名、证件类型、证件号、手机号、备注等)")
|
||||
print("3. 座位信息文件应有10+列(区域、楼层、排号、座位号等)")
|
||||
print("4. 列名应包含相关关键词")
|
||||
print("\n提示: 您可以参考示例文件来准备数据")
|
||||
return False, None, None
|
||||
|
||||
return True, str(personnel_file), str(seat_file)
|
||||
|
||||
# 检查依赖
|
||||
if not check_dependencies():
|
||||
print("❌ 依赖检查失败")
|
||||
input("按Enter键退出...")
|
||||
sys.exit(1)
|
||||
|
||||
class Logger:
|
||||
"""日志记录器"""
|
||||
def __init__(self, log_file='seat_allocation_log.txt'):
|
||||
self.log_file = log_file
|
||||
# 创建log文件夹
|
||||
self.log_dir = Path('log')
|
||||
self.log_dir.mkdir(exist_ok=True)
|
||||
|
||||
# 设置日志文件路径
|
||||
self.log_file = self.log_dir / log_file
|
||||
self.logs = []
|
||||
|
||||
def log(self, message, print_to_console=True):
|
||||
@@ -32,11 +225,15 @@ class Logger:
|
||||
try:
|
||||
with open(self.log_file, 'w', encoding='utf-8') as f:
|
||||
f.write('\n'.join(self.logs))
|
||||
self.log(f"日志已保存到: {self.log_file}")
|
||||
print(f"日志已保存到: {self.log_file}")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"保存日志失败: {e}")
|
||||
return False
|
||||
|
||||
def get_log_path(self, filename):
|
||||
"""获取log文件夹中的文件路径"""
|
||||
return self.log_dir / filename
|
||||
|
||||
class SeatAllocationSystem:
|
||||
"""座位分配系统"""
|
||||
@@ -47,34 +244,245 @@ class SeatAllocationSystem:
|
||||
self.seat_df = None
|
||||
self.seating_groups = []
|
||||
self.row_analysis = {}
|
||||
self.personnel_file = None
|
||||
self.seat_file = None
|
||||
|
||||
def choose_file_mode(self):
|
||||
"""选择文件识别模式"""
|
||||
self.logger.log("\n=== 选择文件识别模式 ===")
|
||||
self.logger.log("1. 默认文件名 - 读取 '人员信息.xlsx' 和 '座位信息.xlsx'")
|
||||
self.logger.log("2. 智能识别 - 自动识别目录中的Excel文件")
|
||||
|
||||
while True:
|
||||
try:
|
||||
choice = input("\n请选择文件识别模式 (1/2,直接回车选择默认): ").strip()
|
||||
|
||||
if choice == '' or choice == '1':
|
||||
self.logger.log("✅ 选择默认文件名模式")
|
||||
return 'default'
|
||||
elif choice == '2':
|
||||
self.logger.log("✅ 选择智能识别模式")
|
||||
return 'smart'
|
||||
else:
|
||||
print("请输入 1 或 2")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
self.logger.log("\n用户取消操作")
|
||||
return 'default'
|
||||
|
||||
def smart_identify_files(self):
|
||||
"""智能识别Excel文件"""
|
||||
self.logger.log("\n=== 智能识别Excel文件 ===")
|
||||
|
||||
# 获取当前目录下的所有Excel文件
|
||||
excel_files = []
|
||||
for ext in ['*.xlsx', '*.xls']:
|
||||
excel_files.extend(Path('.').glob(ext))
|
||||
|
||||
if len(excel_files) < 2:
|
||||
self.logger.log(f"❌ 当前目录只找到 {len(excel_files)} 个Excel文件,需要至少2个")
|
||||
return None, None
|
||||
|
||||
self.logger.log(f"📁 找到 {len(excel_files)} 个Excel文件:")
|
||||
|
||||
# 分析每个文件的内容
|
||||
file_analysis = []
|
||||
for file_path in excel_files:
|
||||
try:
|
||||
# 先读取完整文件获取真实行数
|
||||
df_full = pd.read_excel(file_path)
|
||||
total_rows = len(df_full)
|
||||
|
||||
# 再读取前5行进行列分析
|
||||
df_sample = pd.read_excel(file_path, nrows=5)
|
||||
analysis = {
|
||||
'path': file_path,
|
||||
'name': file_path.name,
|
||||
'rows': total_rows, # 使用真实行数
|
||||
'cols': len(df_sample.columns),
|
||||
'columns': list(df_sample.columns),
|
||||
'score': 0,
|
||||
'type': 'unknown'
|
||||
}
|
||||
|
||||
# 判断文件类型 - 改进的识别算法
|
||||
columns_str = ' '.join(str(col).lower() for col in df_sample.columns)
|
||||
columns_list = [str(col).lower() for col in df_sample.columns]
|
||||
|
||||
# 座位信息文件的强特征(优先判断)
|
||||
seat_strong_keywords = ['座位id', '区域', '楼层', '排号', '座位号']
|
||||
seat_strong_score = sum(1 for keyword in seat_strong_keywords if keyword in columns_str)
|
||||
|
||||
# 人员信息文件的强特征
|
||||
personnel_strong_keywords = ['备注'] # 备注列是人员信息文件的强特征
|
||||
personnel_strong_score = sum(1 for keyword in personnel_strong_keywords if keyword in columns_str)
|
||||
|
||||
# 如果有座位强特征,优先识别为座位文件
|
||||
if seat_strong_score >= 3: # 至少包含3个座位强特征
|
||||
analysis['type'] = 'seat'
|
||||
analysis['score'] = seat_strong_score * 10 # 给予高权重
|
||||
# 如果有人员强特征且列数较少,识别为人员文件
|
||||
elif personnel_strong_score > 0 and len(df_sample.columns) <= 8:
|
||||
analysis['type'] = 'personnel'
|
||||
analysis['score'] = personnel_strong_score * 10
|
||||
else:
|
||||
# 使用原有的弱特征判断
|
||||
personnel_keywords = ['姓名', '证件', '手机', 'name', 'id', 'phone']
|
||||
personnel_score = sum(1 for keyword in personnel_keywords if keyword in columns_str)
|
||||
|
||||
seat_keywords = ['区域', '楼层', '排号', '座位', 'area', 'floor', 'row', 'seat']
|
||||
seat_score = sum(1 for keyword in seat_keywords if keyword in columns_str)
|
||||
|
||||
# 结合列数进行判断
|
||||
if len(df_sample.columns) <= 8 and personnel_score > 0:
|
||||
analysis['type'] = 'personnel'
|
||||
analysis['score'] = personnel_score
|
||||
elif len(df_sample.columns) > 8 and seat_score > 0:
|
||||
analysis['type'] = 'seat'
|
||||
analysis['score'] = seat_score
|
||||
elif personnel_score > seat_score:
|
||||
analysis['type'] = 'personnel'
|
||||
analysis['score'] = personnel_score
|
||||
elif seat_score > personnel_score:
|
||||
analysis['type'] = 'seat'
|
||||
analysis['score'] = seat_score
|
||||
|
||||
file_analysis.append(analysis)
|
||||
|
||||
self.logger.log(f" 📄 {file_path.name}: {total_rows}行 × {len(df_sample.columns)}列")
|
||||
self.logger.log(f" 列名: {', '.join(str(col) for col in df_sample.columns[:5])}{'...' if len(df_sample.columns) > 5 else ''}")
|
||||
self.logger.log(f" 推测类型: {analysis['type']} (得分: {analysis['score']})")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.log(f" ❌ {file_path.name}: 读取失败 - {e}")
|
||||
|
||||
# 自动匹配最佳文件
|
||||
personnel_files = [f for f in file_analysis if f['type'] == 'personnel']
|
||||
seat_files = [f for f in file_analysis if f['type'] == 'seat']
|
||||
|
||||
personnel_file = None
|
||||
seat_file = None
|
||||
|
||||
if personnel_files:
|
||||
personnel_file = max(personnel_files, key=lambda x: x['score'])['path']
|
||||
self.logger.log(f"\n🎯 自动识别人员信息文件: {personnel_file.name}")
|
||||
|
||||
if seat_files:
|
||||
seat_file = max(seat_files, key=lambda x: x['score'])['path']
|
||||
self.logger.log(f"🎯 自动识别座位信息文件: {seat_file.name}")
|
||||
|
||||
# 如果自动识别不完整,提供手动选择
|
||||
if not personnel_file or not seat_file:
|
||||
self.logger.log("\n⚠️ 自动识别不完整,请手动选择:")
|
||||
personnel_file, seat_file = self.manual_select_files(file_analysis)
|
||||
|
||||
return personnel_file, seat_file
|
||||
|
||||
def manual_select_files(self, file_analysis):
|
||||
"""手动选择文件"""
|
||||
self.logger.log("\n=== 手动选择文件 ===")
|
||||
|
||||
print("\n可用的Excel文件:")
|
||||
for i, analysis in enumerate(file_analysis):
|
||||
print(f"{i+1}. {analysis['name']} ({analysis['rows']}行 × {analysis['cols']}列)")
|
||||
print(f" 列名: {', '.join(str(col) for col in analysis['columns'][:3])}...")
|
||||
|
||||
personnel_file = None
|
||||
seat_file = None
|
||||
|
||||
# 选择人员信息文件
|
||||
while not personnel_file:
|
||||
try:
|
||||
choice = input(f"\n请选择人员信息文件 (1-{len(file_analysis)}): ").strip()
|
||||
idx = int(choice) - 1
|
||||
if 0 <= idx < len(file_analysis):
|
||||
personnel_file = file_analysis[idx]['path']
|
||||
self.logger.log(f"✅ 选择人员信息文件: {personnel_file.name}")
|
||||
else:
|
||||
print("请输入有效的数字")
|
||||
except (ValueError, KeyboardInterrupt):
|
||||
print("请输入有效的数字")
|
||||
|
||||
# 选择座位信息文件
|
||||
while not seat_file:
|
||||
try:
|
||||
choice = input(f"\n请选择座位信息文件 (1-{len(file_analysis)}): ").strip()
|
||||
idx = int(choice) - 1
|
||||
if 0 <= idx < len(file_analysis):
|
||||
if file_analysis[idx]['path'] == personnel_file:
|
||||
print("不能选择相同的文件,请重新选择")
|
||||
continue
|
||||
seat_file = file_analysis[idx]['path']
|
||||
self.logger.log(f"✅ 选择座位信息文件: {seat_file.name}")
|
||||
else:
|
||||
print("请输入有效的数字")
|
||||
except (ValueError, KeyboardInterrupt):
|
||||
print("请输入有效的数字")
|
||||
|
||||
return personnel_file, seat_file
|
||||
|
||||
def set_data_files(self, personnel_file, seat_file):
|
||||
"""设置数据文件路径"""
|
||||
self.personnel_file = personnel_file
|
||||
self.seat_file = seat_file
|
||||
|
||||
def identify_and_set_files(self):
|
||||
"""识别并设置数据文件"""
|
||||
file_mode = self.choose_file_mode()
|
||||
|
||||
if file_mode == 'default':
|
||||
# 默认文件名模式
|
||||
personnel_file = Path('人员信息.xlsx')
|
||||
seat_file = Path('座位信息.xlsx')
|
||||
|
||||
if not personnel_file.exists():
|
||||
self.logger.log(f"❌ 错误: {personnel_file} 文件不存在")
|
||||
return False
|
||||
|
||||
if not seat_file.exists():
|
||||
self.logger.log(f"❌ 错误: {seat_file} 文件不存在")
|
||||
return False
|
||||
|
||||
else:
|
||||
# 智能识别模式
|
||||
personnel_file, seat_file = self.smart_identify_files()
|
||||
if not personnel_file or not seat_file:
|
||||
self.logger.log("❌ 文件识别失败")
|
||||
return False
|
||||
|
||||
self.set_data_files(personnel_file, seat_file)
|
||||
return True
|
||||
|
||||
def load_data(self):
|
||||
"""加载人员信息和座位信息数据"""
|
||||
self.logger.log("=== 开始加载数据 ===")
|
||||
|
||||
# 检查文件是否存在
|
||||
personnel_file = '人员信息.xlsx'
|
||||
seat_file = '座位信息.xlsx'
|
||||
|
||||
if not Path(personnel_file).exists():
|
||||
self.logger.log(f"❌ 错误: {personnel_file} 文件不存在")
|
||||
# 使用自动识别的文件路径
|
||||
if not self.personnel_file or not self.seat_file:
|
||||
self.logger.log("❌ 错误: 数据文件路径未设置")
|
||||
return False
|
||||
|
||||
if not Path(seat_file).exists():
|
||||
self.logger.log(f"❌ 错误: {seat_file} 文件不存在")
|
||||
if not Path(self.personnel_file).exists():
|
||||
self.logger.log(f"❌ 错误: {self.personnel_file} 文件不存在")
|
||||
return False
|
||||
|
||||
if not Path(self.seat_file).exists():
|
||||
self.logger.log(f"❌ 错误: {self.seat_file} 文件不存在")
|
||||
return False
|
||||
|
||||
try:
|
||||
self.logger.log(f"正在读取人员信息文件: {self.personnel_file}")
|
||||
# 读取人员信息,指定数据类型
|
||||
self.personnel_df = pd.read_excel(personnel_file, dtype={
|
||||
self.personnel_df = pd.read_excel(self.personnel_file, dtype={
|
||||
'姓名': 'str',
|
||||
'证件类型': 'str',
|
||||
'证件号': 'str', # 证件号作为字符串读取,避免X被转换
|
||||
'手机号': 'str'
|
||||
})
|
||||
|
||||
self.logger.log(f"正在读取座位信息文件: {self.seat_file}")
|
||||
# 读取座位信息,指定数据类型
|
||||
self.seat_df = pd.read_excel(seat_file, dtype={
|
||||
seat_df_raw = pd.read_excel(self.seat_file, dtype={
|
||||
'区域': 'str',
|
||||
'楼层': 'str',
|
||||
'排号': 'str',
|
||||
@@ -85,6 +493,9 @@ class SeatAllocationSystem:
|
||||
'手机号': 'str',
|
||||
'签发地/国籍': 'str'
|
||||
})
|
||||
|
||||
# 过滤和清洗座位数据
|
||||
self.seat_df = self.filter_seat_data(seat_df_raw)
|
||||
|
||||
# 清理文字信息中的空格
|
||||
self.clean_text_data()
|
||||
@@ -117,26 +528,72 @@ class SeatAllocationSystem:
|
||||
|
||||
self.logger.log("✅ 文字数据清理完成")
|
||||
|
||||
def filter_seat_data(self, seat_df_raw):
|
||||
"""过滤和清洗座位数据,移除空行和无效数据"""
|
||||
self.logger.log("开始过滤座位数据...")
|
||||
|
||||
original_count = len(seat_df_raw)
|
||||
self.logger.log(f"原始数据行数: {original_count}")
|
||||
|
||||
# 关键列,这些列都不能为空
|
||||
key_columns = ['区域', '楼层', '排号', '座位号']
|
||||
|
||||
# 创建过滤条件:关键列都不能为空且不能为'nan'字符串
|
||||
filter_condition = True
|
||||
for col in key_columns:
|
||||
if col in seat_df_raw.columns:
|
||||
# 过滤空值、NaN、'nan'字符串
|
||||
col_condition = (
|
||||
seat_df_raw[col].notna() &
|
||||
(seat_df_raw[col].astype(str).str.strip() != '') &
|
||||
(seat_df_raw[col].astype(str).str.lower() != 'nan')
|
||||
)
|
||||
filter_condition = filter_condition & col_condition
|
||||
|
||||
# 应用过滤条件
|
||||
seat_df_filtered = seat_df_raw[filter_condition].copy()
|
||||
|
||||
# 重置索引
|
||||
seat_df_filtered.reset_index(drop=True, inplace=True)
|
||||
|
||||
filtered_count = len(seat_df_filtered)
|
||||
removed_count = original_count - filtered_count
|
||||
|
||||
self.logger.log(f"过滤后数据行数: {filtered_count}")
|
||||
self.logger.log(f"移除无效行数: {removed_count}")
|
||||
|
||||
if removed_count > 0:
|
||||
self.logger.log(f"✅ 已过滤掉 {removed_count} 行无效数据(空行、示例数据等)")
|
||||
|
||||
# 显示过滤后的数据概览
|
||||
if filtered_count > 0:
|
||||
areas = seat_df_filtered['区域'].unique()
|
||||
floors = seat_df_filtered['楼层'].unique()
|
||||
self.logger.log(f"有效座位区域: {', '.join(areas)}")
|
||||
self.logger.log(f"有效座位楼层: {', '.join(floors)}")
|
||||
|
||||
return seat_df_filtered
|
||||
|
||||
def validate_personnel_structure(self):
|
||||
"""校验人员信息文件结构"""
|
||||
self.logger.log("\n=== 人员信息结构校验 ===")
|
||||
|
||||
|
||||
required_columns = ['姓名', '证件类型', '证件号', '手机号', '备注']
|
||||
validation_results = []
|
||||
|
||||
|
||||
# 检查必需列
|
||||
missing_columns = []
|
||||
for col in required_columns:
|
||||
if col not in self.personnel_df.columns:
|
||||
missing_columns.append(col)
|
||||
|
||||
|
||||
if missing_columns:
|
||||
self.logger.log(f"❌ 缺少必需列: {missing_columns}")
|
||||
validation_results.append(False)
|
||||
else:
|
||||
self.logger.log("✅ 所有必需列都存在")
|
||||
validation_results.append(True)
|
||||
|
||||
|
||||
# 检查数据完整性
|
||||
self.logger.log("\n数据完整性检查:")
|
||||
for col in ['姓名', '证件类型', '证件号', '手机号']:
|
||||
@@ -148,16 +605,36 @@ class SeatAllocationSystem:
|
||||
else:
|
||||
self.logger.log(f"✅ {col} 列数据完整")
|
||||
validation_results.append(True)
|
||||
|
||||
# 检查重复姓名
|
||||
duplicate_names = self.personnel_df[self.personnel_df['姓名'].duplicated()]
|
||||
if not duplicate_names.empty:
|
||||
self.logger.log(f"⚠️ 发现重复姓名: {duplicate_names['姓名'].tolist()}")
|
||||
validation_results.append(False)
|
||||
|
||||
# 检查重复人员(姓名+身份证号组合)
|
||||
self.logger.log("\n重复人员检查:")
|
||||
if '姓名' in self.personnel_df.columns and '证件号' in self.personnel_df.columns:
|
||||
# 创建姓名+证件号的组合标识
|
||||
self.personnel_df['人员标识'] = self.personnel_df['姓名'].astype(str) + '_' + self.personnel_df['证件号'].astype(str)
|
||||
duplicate_persons = self.personnel_df[self.personnel_df['人员标识'].duplicated()]
|
||||
|
||||
if not duplicate_persons.empty:
|
||||
self.logger.log(f"⚠️ 发现重复人员:")
|
||||
for _, person in duplicate_persons.iterrows():
|
||||
self.logger.log(f" {person['姓名']} (证件号: {person['证件号']})")
|
||||
validation_results.append(False)
|
||||
else:
|
||||
self.logger.log("✅ 无重复人员(姓名+证件号组合检查)")
|
||||
validation_results.append(True)
|
||||
|
||||
# 单独检查重名情况(仅提示,不影响校验结果)
|
||||
duplicate_names = self.personnel_df[self.personnel_df['姓名'].duplicated(keep=False)]
|
||||
if not duplicate_names.empty:
|
||||
self.logger.log(f"📝 发现重名人员(但证件号不同,允许通过):")
|
||||
name_groups = duplicate_names.groupby('姓名')
|
||||
for name, group in name_groups:
|
||||
self.logger.log(f" 姓名: {name}")
|
||||
for _, person in group.iterrows():
|
||||
self.logger.log(f" 证件号: {person['证件号']}")
|
||||
else:
|
||||
self.logger.log("✅ 姓名无重复")
|
||||
validation_results.append(True)
|
||||
|
||||
self.logger.log("❌ 缺少姓名或证件号列,无法进行重复检查")
|
||||
validation_results.append(False)
|
||||
|
||||
return all(validation_results)
|
||||
|
||||
def validate_seating_groups(self):
|
||||
@@ -431,10 +908,73 @@ class SeatAllocationSystem:
|
||||
|
||||
return True
|
||||
|
||||
def analyze_seating_requirements(self):
|
||||
def analyze_seating_requirements(self, use_phone_grouping=False):
|
||||
"""分析人员连坐需求"""
|
||||
self.logger.log("\n=== 人员连坐需求分析 ===")
|
||||
|
||||
if use_phone_grouping:
|
||||
self.logger.log("🔍 使用手机号分组模式")
|
||||
return self.analyze_seating_by_phone()
|
||||
else:
|
||||
self.logger.log("🔍 使用备注分组模式")
|
||||
return self.analyze_seating_by_remark()
|
||||
|
||||
def analyze_seating_by_phone(self):
|
||||
"""基于手机号分析连坐需求"""
|
||||
self.logger.log("根据相同手机号识别连坐组...")
|
||||
|
||||
# 按手机号分组
|
||||
phone_groups = self.personnel_df.groupby('手机号')
|
||||
self.seating_groups = []
|
||||
|
||||
for phone, group in phone_groups:
|
||||
group_members = [row for _, row in group.iterrows()]
|
||||
group_size = len(group_members)
|
||||
|
||||
if group_size == 1:
|
||||
# 单人组
|
||||
self.seating_groups.append({
|
||||
'type': 'single',
|
||||
'size': 1,
|
||||
'members': group_members,
|
||||
'leader': group_members[0]['姓名'],
|
||||
'grouping_method': 'phone'
|
||||
})
|
||||
self.logger.log(f"📱 单人: {group_members[0]['姓名']} (手机号: {phone})")
|
||||
else:
|
||||
# 连坐组
|
||||
leader_name = group_members[0]['姓名']
|
||||
member_names = [member['姓名'] for member in group_members]
|
||||
|
||||
self.seating_groups.append({
|
||||
'type': 'group',
|
||||
'size': group_size,
|
||||
'members': group_members,
|
||||
'leader': leader_name,
|
||||
'grouping_method': 'phone'
|
||||
})
|
||||
self.logger.log(f"📱 连坐组: {', '.join(member_names)} (手机号: {phone}, {group_size}人)")
|
||||
|
||||
# 统计
|
||||
size_stats = {}
|
||||
for group in self.seating_groups:
|
||||
size = group['size']
|
||||
size_stats[size] = size_stats.get(size, 0) + 1
|
||||
|
||||
self.logger.log(f"\n基于手机号识别出 {len(self.seating_groups)} 个座位组:")
|
||||
for size in sorted(size_stats.keys()):
|
||||
count = size_stats[size]
|
||||
if size == 1:
|
||||
self.logger.log(f" 单人组: {count} 个")
|
||||
else:
|
||||
self.logger.log(f" {size}人连坐组: {count} 个")
|
||||
|
||||
return True
|
||||
|
||||
def analyze_seating_by_remark(self):
|
||||
"""基于备注分析连坐需求"""
|
||||
self.logger.log("根据备注数字识别连坐组...")
|
||||
|
||||
self.seating_groups = []
|
||||
i = 0
|
||||
|
||||
@@ -448,7 +988,8 @@ class SeatAllocationSystem:
|
||||
'type': 'single',
|
||||
'size': 1,
|
||||
'members': [person],
|
||||
'leader': person['姓名']
|
||||
'leader': person['姓名'],
|
||||
'grouping_method': 'remark'
|
||||
})
|
||||
i += 1
|
||||
else:
|
||||
@@ -465,7 +1006,8 @@ class SeatAllocationSystem:
|
||||
'type': 'group' if group_size > 1 else 'single',
|
||||
'size': len(group_members),
|
||||
'members': group_members,
|
||||
'leader': person['姓名']
|
||||
'leader': person['姓名'],
|
||||
'grouping_method': 'remark'
|
||||
})
|
||||
|
||||
i += group_size
|
||||
@@ -476,7 +1018,7 @@ class SeatAllocationSystem:
|
||||
size = group['size']
|
||||
size_stats[size] = size_stats.get(size, 0) + 1
|
||||
|
||||
self.logger.log(f"总共识别出 {len(self.seating_groups)} 个座位组:")
|
||||
self.logger.log(f"\n基于备注识别出 {len(self.seating_groups)} 个座位组:")
|
||||
for size in sorted(size_stats.keys()):
|
||||
count = size_stats[size]
|
||||
if size == 1:
|
||||
@@ -599,13 +1141,14 @@ class SeatAllocationSystem:
|
||||
person_info = seating_group['members'][0]
|
||||
seat_index = seat_info['index']
|
||||
|
||||
# 更新座位信息,确保数据类型正确
|
||||
# 覆盖座位表中的人员信息列
|
||||
seat_df_copy.loc[seat_index, '姓名'] = str(person_info['姓名']).strip()
|
||||
seat_df_copy.loc[seat_index, '证件类型'] = str(person_info['证件类型']).strip()
|
||||
seat_df_copy.loc[seat_index, '证件号'] = str(person_info['证件号']).strip()
|
||||
seat_df_copy.loc[seat_index, '手机国家号'] = person_info.get('Unnamed: 3', 86)
|
||||
seat_df_copy.loc[seat_index, '手机号'] = str(person_info['手机号']).strip()
|
||||
seat_df_copy.loc[seat_index, '签发地/国籍'] = str(person_info.get('备注', '')).strip()
|
||||
# 只有分配备注放在第13列
|
||||
seat_df_copy.loc[seat_index, '分配备注'] = str(person_info.get('备注', '')).strip()
|
||||
|
||||
assignment_log.append({
|
||||
'组号': group_idx + 1,
|
||||
@@ -683,7 +1226,8 @@ class SeatAllocationSystem:
|
||||
seat_df_copy.loc[seat_index, '证件号'] = str(person_info['证件号']).strip()
|
||||
seat_df_copy.loc[seat_index, '手机国家号'] = person_info.get('Unnamed: 3', 86)
|
||||
seat_df_copy.loc[seat_index, '手机号'] = str(person_info['手机号']).strip()
|
||||
seat_df_copy.loc[seat_index, '签发地/国籍'] = str(person_info.get('备注', '')).strip()
|
||||
# 只有分配备注放在第13列
|
||||
seat_df_copy.loc[seat_index, '分配备注'] = str(person_info.get('备注', '')).strip()
|
||||
|
||||
assignment_log.append({
|
||||
'组号': group_idx + 1,
|
||||
@@ -725,14 +1269,23 @@ class SeatAllocationSystem:
|
||||
"""保存分配结果"""
|
||||
try:
|
||||
# 保存更新后的座位信息
|
||||
output_file = '座位信息_最终分配.xlsx'
|
||||
output_file = self.logger.get_log_path('座位信息_最终分配.xlsx')
|
||||
seat_df_result.to_excel(output_file, index=False)
|
||||
self.logger.log(f"\n座位分配结果已保存到: {output_file}")
|
||||
self.logger.log(f"📋 人员信息已覆盖到座位表对应列:")
|
||||
self.logger.log(f" 第6列: 姓名")
|
||||
self.logger.log(f" 第7列: 证件类型")
|
||||
self.logger.log(f" 第8列: 证件号")
|
||||
self.logger.log(f" 第9列: 手机国家号")
|
||||
self.logger.log(f" 第10列: 手机号")
|
||||
self.logger.log(f"📝 分配备注信息:")
|
||||
self.logger.log(f" 第13列: 分配备注(新增列,不覆盖原数据)")
|
||||
self.logger.log(f"💡 座位基本信息(第1-5列)保持不变")
|
||||
|
||||
# 保存分配日志
|
||||
if assignment_log:
|
||||
log_df = pd.DataFrame(assignment_log)
|
||||
log_file = '最终座位分配日志.xlsx'
|
||||
log_file = self.logger.get_log_path('最终座位分配日志.xlsx')
|
||||
log_df.to_excel(log_file, index=False)
|
||||
self.logger.log(f"分配日志已保存到: {log_file}")
|
||||
|
||||
@@ -805,6 +1358,10 @@ class SeatAllocationSystem:
|
||||
self.logger.log("座位分配系统 - 文件校验")
|
||||
self.logger.log("=" * 60)
|
||||
|
||||
# 识别并设置数据文件
|
||||
if not self.identify_and_set_files():
|
||||
return False
|
||||
|
||||
# 加载数据
|
||||
if not self.load_data():
|
||||
return False
|
||||
@@ -858,8 +1415,11 @@ class SeatAllocationSystem:
|
||||
self.logger.log("开始座位分配")
|
||||
self.logger.log("=" * 60)
|
||||
|
||||
# 选择分组方式
|
||||
grouping_method = self.choose_grouping_method()
|
||||
|
||||
# 分析人员连坐需求
|
||||
if not self.analyze_seating_requirements():
|
||||
if not self.analyze_seating_requirements(use_phone_grouping=(grouping_method == 'phone')):
|
||||
return False
|
||||
|
||||
# 高级座位结构分析
|
||||
@@ -877,22 +1437,83 @@ class SeatAllocationSystem:
|
||||
self.logger.log("\n❌ 座位分配失败!")
|
||||
return False
|
||||
|
||||
def choose_grouping_method(self):
|
||||
"""选择分组方式"""
|
||||
self.logger.log("\n=== 选择连坐分组方式 ===")
|
||||
self.logger.log("1. 备注分组 - 根据备注数字识别连坐组(默认)")
|
||||
self.logger.log("2. 手机号分组 - 根据相同手机号识别连坐组")
|
||||
|
||||
while True:
|
||||
try:
|
||||
choice = input("\n请选择分组方式 (1/2,直接回车选择默认): ").strip()
|
||||
|
||||
if choice == '' or choice == '1':
|
||||
self.logger.log("✅ 选择备注分组模式")
|
||||
return 'remark'
|
||||
elif choice == '2':
|
||||
self.logger.log("✅ 选择手机号分组模式")
|
||||
return 'phone'
|
||||
else:
|
||||
print("请输入 1 或 2")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
self.logger.log("\n用户取消操作")
|
||||
return 'remark'
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
system = SeatAllocationSystem()
|
||||
|
||||
print("=" * 60)
|
||||
print("座位分配系统 v2.0 (智能识别版)")
|
||||
print("=" * 60)
|
||||
|
||||
try:
|
||||
# 自动检查和识别数据文件
|
||||
success, personnel_file, seat_file = check_data_files()
|
||||
if not success:
|
||||
input("\n按Enter键退出...")
|
||||
return
|
||||
|
||||
print("\n开始运行座位分配系统...")
|
||||
system = SeatAllocationSystem()
|
||||
|
||||
# 设置识别出的文件路径
|
||||
system.set_data_files(personnel_file, seat_file)
|
||||
|
||||
# 运行校验
|
||||
if system.run_validation():
|
||||
# 校验通过,运行分配
|
||||
system.run_allocation()
|
||||
# 校验通过,询问是否继续分配
|
||||
response = input("\n文件校验通过,是否开始座位分配? (Y/n): ")
|
||||
if response.lower() in ['', 'y', 'yes']:
|
||||
# 运行分配
|
||||
if system.run_allocation():
|
||||
print("\n🎉 座位分配完成!")
|
||||
print("请查看log文件夹中的输出文件:")
|
||||
print("- log/座位信息_最终分配.xlsx (分配结果)")
|
||||
print("- log/最终座位分配日志.xlsx (详细日志)")
|
||||
print("- log/seat_allocation_log.txt (运行日志)")
|
||||
else:
|
||||
print("\n❌ 座位分配失败!")
|
||||
else:
|
||||
print("用户取消座位分配")
|
||||
else:
|
||||
print("\n❌ 文件校验失败,请修复问题后重试")
|
||||
|
||||
# 保存日志
|
||||
system.logger.save_logs()
|
||||
|
||||
except FileNotFoundError as e:
|
||||
print(f"\n❌ 文件未找到: {e}")
|
||||
print("请确保所有必要文件都在程序目录下")
|
||||
except PermissionError as e:
|
||||
print(f"\n❌ 文件权限错误: {e}")
|
||||
print("请确保程序有读写文件的权限")
|
||||
except Exception as e:
|
||||
system.logger.log(f"系统运行出错: {e}")
|
||||
system.logger.save_logs()
|
||||
print(f"\n❌ 程序运行出错: {e}")
|
||||
print("请检查数据文件格式是否正确")
|
||||
|
||||
finally:
|
||||
# 等待用户确认后退出
|
||||
input("\n程序结束,按Enter键退出...")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
232
simple_build.py
232
simple_build.py
@@ -1,232 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
简化构建脚本
|
||||
在当前平台构建可执行文件
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import platform
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
def get_platform_info():
|
||||
"""获取平台信息"""
|
||||
system = platform.system()
|
||||
machine = platform.machine().lower()
|
||||
|
||||
if system == 'Windows':
|
||||
if machine in ['amd64', 'x86_64']:
|
||||
return 'windows_x64'
|
||||
elif machine in ['i386', 'i686', 'x86']:
|
||||
return 'windows_x86'
|
||||
else:
|
||||
return f'windows_{machine}'
|
||||
elif system == 'Darwin':
|
||||
if machine in ['arm64', 'aarch64']:
|
||||
return 'macos_arm64'
|
||||
else:
|
||||
return 'macos_x64'
|
||||
elif system == 'Linux':
|
||||
return f'linux_{machine}'
|
||||
else:
|
||||
return f'{system.lower()}_{machine}'
|
||||
|
||||
def install_dependencies():
|
||||
"""安装依赖"""
|
||||
print("安装依赖包...")
|
||||
packages = ['pandas', 'openpyxl', 'pyinstaller']
|
||||
|
||||
for package in packages:
|
||||
try:
|
||||
__import__(package)
|
||||
print(f"✅ {package} 已安装")
|
||||
except ImportError:
|
||||
print(f"📦 安装 {package}...")
|
||||
try:
|
||||
subprocess.check_call([sys.executable, '-m', 'pip', 'install', package])
|
||||
print(f"✅ {package} 安装成功")
|
||||
except subprocess.CalledProcessError:
|
||||
print(f"❌ {package} 安装失败")
|
||||
return False
|
||||
return True
|
||||
|
||||
def build_executable():
|
||||
"""构建可执行文件"""
|
||||
print("\n开始构建可执行文件...")
|
||||
|
||||
# 获取平台信息
|
||||
platform_name = get_platform_info()
|
||||
exe_name = f"座位分配系统_{platform_name}"
|
||||
|
||||
print(f"目标平台: {platform_name}")
|
||||
print(f"输出文件: {exe_name}")
|
||||
|
||||
# 清理之前的构建
|
||||
if os.path.exists('dist'):
|
||||
shutil.rmtree('dist')
|
||||
if os.path.exists('build'):
|
||||
shutil.rmtree('build')
|
||||
|
||||
# 构建命令
|
||||
cmd = [
|
||||
sys.executable, '-m', 'PyInstaller',
|
||||
'--onefile',
|
||||
'--console',
|
||||
'--clean',
|
||||
'--name', exe_name,
|
||||
'seat_allocation_system.py'
|
||||
]
|
||||
|
||||
try:
|
||||
print(f"执行命令: {' '.join(cmd)}")
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
|
||||
if result.returncode == 0:
|
||||
print("✅ 构建成功!")
|
||||
|
||||
# 检查生成的文件
|
||||
if platform.system() == 'Windows':
|
||||
exe_path = Path('dist') / f'{exe_name}.exe'
|
||||
else:
|
||||
exe_path = Path('dist') / exe_name
|
||||
|
||||
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("❌ 未找到生成的文件")
|
||||
return False, None
|
||||
else:
|
||||
print("❌ 构建失败!")
|
||||
print("错误输出:")
|
||||
print(result.stderr)
|
||||
return False, None
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 构建过程中出现错误: {e}")
|
||||
return False, None
|
||||
|
||||
def create_distribution():
|
||||
"""创建分发包"""
|
||||
print("\n创建分发包...")
|
||||
|
||||
platform_name = get_platform_info()
|
||||
package_name = f"座位分配系统_{platform_name}_分发包"
|
||||
package_dir = Path(package_name)
|
||||
|
||||
# 创建分发目录
|
||||
if package_dir.exists():
|
||||
shutil.rmtree(package_dir)
|
||||
package_dir.mkdir()
|
||||
|
||||
# 复制可执行文件
|
||||
dist_dir = Path('dist')
|
||||
if dist_dir.exists():
|
||||
for file in dist_dir.iterdir():
|
||||
if file.is_file():
|
||||
shutil.copy2(file, package_dir)
|
||||
print(f"复制文件: {file.name}")
|
||||
|
||||
# 复制示例文件
|
||||
if Path('人员信息.xlsx').exists():
|
||||
shutil.copy2('人员信息.xlsx', package_dir / '人员信息_示例.xlsx')
|
||||
print("复制示例文件: 人员信息_示例.xlsx")
|
||||
|
||||
if Path('座位信息.xlsx').exists():
|
||||
shutil.copy2('座位信息.xlsx', package_dir / '座位信息_示例.xlsx')
|
||||
print("复制示例文件: 座位信息_示例.xlsx")
|
||||
|
||||
# 创建使用说明
|
||||
readme_content = f"""座位分配系统 使用说明
|
||||
|
||||
平台: {platform_name}
|
||||
构建时间: {platform.platform()}
|
||||
|
||||
使用方法:
|
||||
1. 准备Excel文件
|
||||
- 人员信息.xlsx: 包含姓名、证件信息、备注等
|
||||
- 座位信息.xlsx: 包含区域、楼层、排号、座位号等
|
||||
|
||||
2. 运行程序
|
||||
- 将可执行文件放在Excel文件同一目录
|
||||
- 双击运行可执行文件
|
||||
- 等待处理完成
|
||||
|
||||
3. 查看结果
|
||||
- 座位信息_最终分配.xlsx: 分配结果
|
||||
- 最终座位分配日志.xlsx: 详细记录
|
||||
- seat_allocation_log.txt: 运行日志
|
||||
|
||||
功能特点:
|
||||
- 支持1-10人连坐需求
|
||||
- 自动处理不连续座位
|
||||
- 完整的数据校验
|
||||
- 详细的分配日志
|
||||
|
||||
注意事项:
|
||||
- 确保Excel文件格式正确
|
||||
- 备注数字表示连坐人数
|
||||
- 运行时会在同目录生成结果文件
|
||||
"""
|
||||
|
||||
with open(package_dir / '使用说明.txt', 'w', encoding='utf-8') as f:
|
||||
f.write(readme_content)
|
||||
|
||||
print(f"✅ 分发包已创建: {package_dir}")
|
||||
return package_dir
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("座位分配系统 - 简化构建工具")
|
||||
print("=" * 50)
|
||||
|
||||
# 显示系统信息
|
||||
print(f"系统: {platform.system()} {platform.release()}")
|
||||
print(f"架构: {platform.machine()}")
|
||||
print(f"Python: {sys.version}")
|
||||
|
||||
# 检查主程序文件
|
||||
if not os.path.exists('seat_allocation_system.py'):
|
||||
print("❌ 未找到 seat_allocation_system.py 文件")
|
||||
return
|
||||
|
||||
# 安装依赖
|
||||
if not install_dependencies():
|
||||
print("❌ 依赖安装失败")
|
||||
return
|
||||
|
||||
# 构建可执行文件
|
||||
success, exe_path = build_executable()
|
||||
if not success:
|
||||
print("❌ 构建失败")
|
||||
return
|
||||
|
||||
# 创建分发包
|
||||
package_dir = create_distribution()
|
||||
|
||||
print("\n🎉 构建完成!")
|
||||
print(f"✅ 可执行文件: {exe_path}")
|
||||
print(f"✅ 分发包: {package_dir}")
|
||||
|
||||
# 平台特定说明
|
||||
if platform.system() == 'Windows':
|
||||
print("\n📝 Windows使用说明:")
|
||||
print("- 可以直接在Windows系统上运行")
|
||||
print("- 确保安装了Visual C++ Redistributable")
|
||||
elif platform.system() == 'Darwin':
|
||||
print("\n📝 macOS使用说明:")
|
||||
print("- 只能在macOS系统上运行")
|
||||
print("- 如需Windows版本,请在Windows系统上构建")
|
||||
print("- 或使用GitHub Actions自动构建")
|
||||
else:
|
||||
print(f"\n📝 {platform.system()}使用说明:")
|
||||
print("- 只能在相同系统上运行")
|
||||
print("- 如需其他平台版本,请在对应系统上构建")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
196
simple_windows_build.py
Normal file
196
simple_windows_build.py
Normal file
@@ -0,0 +1,196 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
简化的Windows构建脚本
|
||||
专门解决PyInstaller依赖问题
|
||||
"""
|
||||
|
||||
import sys
|
||||
import subprocess
|
||||
import platform
|
||||
from pathlib import Path
|
||||
|
||||
def create_simple_spec():
|
||||
"""创建简化的spec文件"""
|
||||
spec_content = '''# -*- mode: python ; coding: utf-8 -*-
|
||||
|
||||
block_cipher = None
|
||||
|
||||
a = Analysis(
|
||||
['seat_allocation_system.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[],
|
||||
hiddenimports=[
|
||||
# 核心依赖
|
||||
'pandas',
|
||||
'openpyxl',
|
||||
'numpy',
|
||||
'datetime',
|
||||
'pathlib',
|
||||
'sys',
|
||||
'os',
|
||||
|
||||
# 修复关键错误
|
||||
'numpy.core._methods',
|
||||
'pandas._libs.window.aggregations',
|
||||
'ctypes.util',
|
||||
'pkg_resources.py2_warn',
|
||||
|
||||
# pandas核心
|
||||
'pandas._libs',
|
||||
'pandas._libs.tslibs',
|
||||
'pandas._libs.tslibs.base',
|
||||
'pandas.io.excel',
|
||||
'pandas.io.common',
|
||||
|
||||
# numpy核心
|
||||
'numpy.core',
|
||||
'numpy.core.multiarray',
|
||||
'numpy.core.umath',
|
||||
|
||||
# openpyxl
|
||||
'openpyxl.workbook',
|
||||
'openpyxl.worksheet',
|
||||
'openpyxl.styles',
|
||||
|
||||
# 编码
|
||||
'encodings.utf_8',
|
||||
'encodings.gbk',
|
||||
],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[
|
||||
'matplotlib',
|
||||
'scipy',
|
||||
'IPython',
|
||||
'jupyter',
|
||||
'tkinter',
|
||||
'PyQt5',
|
||||
'PyQt6',
|
||||
'PIL',
|
||||
'cv2',
|
||||
],
|
||||
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 = Path('simple_build.spec')
|
||||
with open(spec_file, 'w', encoding='utf-8') as f:
|
||||
f.write(spec_content)
|
||||
|
||||
print(f"✅ 创建spec文件: {spec_file}")
|
||||
return spec_file
|
||||
|
||||
def build_exe():
|
||||
"""构建可执行文件"""
|
||||
print("=" * 60)
|
||||
print("简化Windows构建")
|
||||
print("=" * 60)
|
||||
|
||||
# 检查环境
|
||||
if platform.system() != 'Windows':
|
||||
print("❌ 此脚本仅适用于Windows")
|
||||
return False
|
||||
|
||||
print(f"✅ 系统: {platform.system()} {platform.release()}")
|
||||
print(f"✅ Python: {sys.version}")
|
||||
|
||||
# 检查主文件
|
||||
main_file = Path('seat_allocation_system.py')
|
||||
if not main_file.exists():
|
||||
print("❌ 未找到主程序文件")
|
||||
return False
|
||||
|
||||
print(f"✅ 主程序: {main_file}")
|
||||
|
||||
# 创建spec文件
|
||||
spec_file = create_simple_spec()
|
||||
|
||||
# 构建命令
|
||||
cmd = [
|
||||
sys.executable, '-m', 'PyInstaller',
|
||||
'--clean',
|
||||
'--noconfirm',
|
||||
str(spec_file)
|
||||
]
|
||||
|
||||
print(f"\n执行命令: {' '.join(cmd)}")
|
||||
print("开始构建...")
|
||||
|
||||
try:
|
||||
# 执行构建
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding='utf-8',
|
||||
errors='ignore'
|
||||
)
|
||||
|
||||
# 显示结果
|
||||
if result.returncode == 0:
|
||||
print("✅ 构建成功!")
|
||||
|
||||
# 检查生成的文件
|
||||
exe_path = Path('dist/座位分配系统.exe')
|
||||
if exe_path.exists():
|
||||
size_mb = exe_path.stat().st_size / (1024 * 1024)
|
||||
print(f"✅ 生成文件: {exe_path}")
|
||||
print(f"✅ 文件大小: {size_mb:.1f} MB")
|
||||
return True
|
||||
else:
|
||||
print("❌ 未找到生成的exe文件")
|
||||
return False
|
||||
else:
|
||||
print("❌ 构建失败!")
|
||||
print("错误输出:")
|
||||
print(result.stderr)
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 构建出错: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
success = build_exe()
|
||||
|
||||
if success:
|
||||
print("\n🎉 构建完成!")
|
||||
print("可执行文件位置: dist/座位分配系统.exe")
|
||||
else:
|
||||
print("\n❌ 构建失败!")
|
||||
|
||||
input("\n按Enter键退出...")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
101
test_build.py
Normal file
101
test_build.py
Normal file
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
测试构建脚本 - 验证依赖和配置
|
||||
"""
|
||||
|
||||
import sys
|
||||
import subprocess
|
||||
import platform
|
||||
|
||||
def test_dependencies():
|
||||
"""测试关键依赖"""
|
||||
print("=" * 50)
|
||||
print("测试关键依赖")
|
||||
print("=" * 50)
|
||||
|
||||
dependencies = [
|
||||
'pandas',
|
||||
'numpy',
|
||||
'openpyxl',
|
||||
'pyinstaller'
|
||||
]
|
||||
|
||||
for dep in dependencies:
|
||||
try:
|
||||
__import__(dep)
|
||||
print(f"✅ {dep} - 已安装")
|
||||
except ImportError:
|
||||
print(f"❌ {dep} - 未安装")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def test_hidden_imports():
|
||||
"""测试隐藏导入"""
|
||||
print("\n" + "=" * 50)
|
||||
print("测试隐藏导入")
|
||||
print("=" * 50)
|
||||
|
||||
hidden_imports = [
|
||||
'numpy.core._methods',
|
||||
'pandas._libs.window.aggregations',
|
||||
'ctypes.util'
|
||||
]
|
||||
|
||||
for imp in hidden_imports:
|
||||
try:
|
||||
parts = imp.split('.')
|
||||
module = __import__(parts[0])
|
||||
for part in parts[1:]:
|
||||
module = getattr(module, part)
|
||||
print(f"✅ {imp} - 可访问")
|
||||
except (ImportError, AttributeError) as e:
|
||||
print(f"⚠️ {imp} - 无法访问: {e}")
|
||||
|
||||
return True
|
||||
|
||||
def test_pyinstaller():
|
||||
"""测试PyInstaller"""
|
||||
print("\n" + "=" * 50)
|
||||
print("测试PyInstaller")
|
||||
print("=" * 50)
|
||||
|
||||
try:
|
||||
result = subprocess.run([
|
||||
sys.executable, '-m', 'PyInstaller', '--version'
|
||||
], capture_output=True, text=True)
|
||||
|
||||
if result.returncode == 0:
|
||||
version = result.stdout.strip()
|
||||
print(f"✅ PyInstaller版本: {version}")
|
||||
return True
|
||||
else:
|
||||
print(f"❌ PyInstaller测试失败: {result.stderr}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"❌ PyInstaller测试出错: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print(f"Python版本: {sys.version}")
|
||||
print(f"操作系统: {platform.system()} {platform.release()}")
|
||||
print(f"架构: {platform.machine()}")
|
||||
|
||||
success = True
|
||||
success &= test_dependencies()
|
||||
success &= test_hidden_imports()
|
||||
success &= test_pyinstaller()
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
if success:
|
||||
print("✅ 所有测试通过,可以进行构建")
|
||||
else:
|
||||
print("❌ 存在问题,请先解决依赖问题")
|
||||
print("=" * 50)
|
||||
|
||||
return success
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
169
test_pandas_dll.py
Normal file
169
test_pandas_dll.py
Normal file
@@ -0,0 +1,169 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
测试pandas DLL模块加载
|
||||
专门检查可能导致PyInstaller问题的模块
|
||||
"""
|
||||
|
||||
import sys
|
||||
import traceback
|
||||
from pathlib import Path
|
||||
|
||||
def test_pandas_core():
|
||||
"""测试pandas核心模块"""
|
||||
print("=" * 50)
|
||||
print("测试pandas核心模块")
|
||||
print("=" * 50)
|
||||
|
||||
modules_to_test = [
|
||||
'pandas',
|
||||
'pandas._libs',
|
||||
'pandas._libs.lib',
|
||||
'pandas._libs.hashtable',
|
||||
'pandas._libs.algos',
|
||||
'pandas._libs.index',
|
||||
'pandas._libs.writers',
|
||||
'pandas._libs.parsers',
|
||||
'pandas._libs.tslibs',
|
||||
'pandas._libs.tslibs.base',
|
||||
'pandas._libs.window',
|
||||
'pandas._libs.window.aggregations', # 关键模块!
|
||||
'pandas._libs.window.indexers',
|
||||
]
|
||||
|
||||
success_count = 0
|
||||
for module in modules_to_test:
|
||||
try:
|
||||
__import__(module)
|
||||
print(f"✅ {module}")
|
||||
success_count += 1
|
||||
except ImportError as e:
|
||||
print(f"❌ {module}: {e}")
|
||||
except Exception as e:
|
||||
print(f"⚠️ {module}: {e}")
|
||||
|
||||
print(f"\n成功加载: {success_count}/{len(modules_to_test)} 个模块")
|
||||
return success_count == len(modules_to_test)
|
||||
|
||||
def test_pandas_functionality():
|
||||
"""测试pandas基本功能"""
|
||||
print("\n" + "=" * 50)
|
||||
print("测试pandas基本功能")
|
||||
print("=" * 50)
|
||||
|
||||
try:
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
|
||||
# 创建测试数据
|
||||
df = pd.DataFrame({
|
||||
'A': [1, 2, 3, 4, 5],
|
||||
'B': [10, 20, 30, 40, 50]
|
||||
})
|
||||
|
||||
print("✅ DataFrame创建成功")
|
||||
|
||||
# 测试窗口函数(这是出错的关键)
|
||||
result = df['A'].rolling(window=2).sum()
|
||||
print("✅ rolling窗口函数正常")
|
||||
|
||||
# 测试聚合函数
|
||||
agg_result = df.groupby('A').agg({'B': 'sum'})
|
||||
print("✅ groupby聚合函数正常")
|
||||
|
||||
# 测试Excel读写
|
||||
test_file = Path('test_pandas.xlsx')
|
||||
df.to_excel(test_file, index=False)
|
||||
df_read = pd.read_excel(test_file)
|
||||
test_file.unlink() # 删除测试文件
|
||||
print("✅ Excel读写功能正常")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ pandas功能测试失败: {e}")
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
def check_pandas_installation():
|
||||
"""检查pandas安装信息"""
|
||||
print("\n" + "=" * 50)
|
||||
print("pandas安装信息")
|
||||
print("=" * 50)
|
||||
|
||||
try:
|
||||
import pandas as pd
|
||||
print(f"✅ pandas版本: {pd.__version__}")
|
||||
print(f"✅ pandas路径: {pd.__file__}")
|
||||
|
||||
# 检查_libs目录
|
||||
pandas_path = Path(pd.__file__).parent
|
||||
libs_path = pandas_path / '_libs'
|
||||
|
||||
if libs_path.exists():
|
||||
print(f"✅ _libs目录存在: {libs_path}")
|
||||
|
||||
# 统计.pyd文件
|
||||
pyd_files = list(libs_path.glob('**/*.pyd'))
|
||||
print(f"✅ 找到 {len(pyd_files)} 个.pyd文件")
|
||||
|
||||
# 检查关键文件
|
||||
key_files = [
|
||||
'window/aggregations.cp310-win_amd64.pyd',
|
||||
'window/indexers.cp310-win_amd64.pyd',
|
||||
'hashtable.cp310-win_amd64.pyd',
|
||||
'lib.cp310-win_amd64.pyd'
|
||||
]
|
||||
|
||||
for key_file in key_files:
|
||||
file_path = libs_path / key_file
|
||||
if file_path.exists():
|
||||
print(f"✅ 关键文件存在: {key_file}")
|
||||
else:
|
||||
print(f"❌ 关键文件缺失: {key_file}")
|
||||
else:
|
||||
print(f"❌ _libs目录不存在: {libs_path}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 检查pandas安装失败: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("🔍 pandas DLL模块测试工具")
|
||||
print("用于诊断PyInstaller构建问题")
|
||||
print()
|
||||
|
||||
# 基本信息
|
||||
print(f"Python版本: {sys.version}")
|
||||
print(f"平台: {sys.platform}")
|
||||
|
||||
# 运行测试
|
||||
install_ok = check_pandas_installation()
|
||||
core_ok = test_pandas_core()
|
||||
func_ok = test_pandas_functionality()
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("测试结果总结")
|
||||
print("=" * 50)
|
||||
|
||||
if install_ok and core_ok and func_ok:
|
||||
print("✅ 所有测试通过!pandas安装正常")
|
||||
print("💡 如果PyInstaller仍有问题,请使用 fix_dll_build.py")
|
||||
else:
|
||||
print("❌ 存在问题,建议:")
|
||||
if not install_ok:
|
||||
print(" 1. 重新安装pandas: pip uninstall pandas && pip install pandas")
|
||||
if not core_ok:
|
||||
print(" 2. 检查pandas C扩展是否正确编译")
|
||||
if not func_ok:
|
||||
print(" 3. 检查依赖库(numpy等)是否正常")
|
||||
|
||||
print("=" * 50)
|
||||
input("\n按Enter键退出...")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
710
windows_build.py
Normal file
710
windows_build.py
Normal file
@@ -0,0 +1,710 @@
|
||||
#!/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>=5.0',
|
||||
'pywin32>=227' # Windows特定依赖
|
||||
]
|
||||
|
||||
for package in required_packages:
|
||||
print(f"\n检查 {package}...")
|
||||
package_name = package.split('>=')[0]
|
||||
|
||||
# 特殊处理pywin32
|
||||
if package_name == 'pywin32':
|
||||
try:
|
||||
import win32api
|
||||
print(f"✅ {package_name} 已安装")
|
||||
continue
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
__import__(package_name)
|
||||
print(f"✅ {package_name} 已安装")
|
||||
continue
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
print(f"📦 安装 {package}...")
|
||||
try:
|
||||
cmd = [sys.executable, '-m', 'pip', 'install', package, '--upgrade']
|
||||
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}")
|
||||
# 对于pywin32,尝试替代安装方法
|
||||
if package_name == 'pywin32':
|
||||
print("尝试使用conda安装pywin32...")
|
||||
try:
|
||||
cmd_conda = ['conda', 'install', '-y', 'pywin32']
|
||||
result_conda = subprocess.run(cmd_conda, capture_output=True, text=True)
|
||||
if result_conda.returncode == 0:
|
||||
print("✅ pywin32 通过conda安装成功")
|
||||
continue
|
||||
except:
|
||||
pass
|
||||
print("⚠️ pywin32安装失败,但可能不影响构建")
|
||||
continue
|
||||
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',
|
||||
'pandas._libs.tslibs',
|
||||
'pandas._libs.tslibs.base',
|
||||
'pandas._libs.tslibs.timedeltas',
|
||||
'pandas._libs.tslibs.np_datetime',
|
||||
'pandas._libs.tslibs.nattype',
|
||||
'pandas._libs.window',
|
||||
'pandas._libs.window.aggregations',
|
||||
'pandas._libs.hashtable',
|
||||
'pandas._libs.algos',
|
||||
'pandas._libs.index',
|
||||
'pandas._libs.lib',
|
||||
'pandas._libs.missing',
|
||||
'pandas._libs.parsers',
|
||||
'pandas._libs.reduction',
|
||||
'pandas._libs.reshape',
|
||||
'pandas._libs.sparse',
|
||||
'pandas._libs.testing',
|
||||
'pandas._libs.writers',
|
||||
|
||||
# numpy相关 - 完整导入
|
||||
'numpy.core',
|
||||
'numpy.core.multiarray',
|
||||
'numpy.core.umath',
|
||||
'numpy.core._methods',
|
||||
'numpy.core._dtype_ctypes',
|
||||
'numpy.core._internal',
|
||||
'numpy.core.numeric',
|
||||
'numpy.core.numerictypes',
|
||||
'numpy.core.function_base',
|
||||
'numpy.core.machar',
|
||||
'numpy.core.getlimits',
|
||||
'numpy.core.shape_base',
|
||||
'numpy.lib.format',
|
||||
'numpy.lib.mixins',
|
||||
'numpy.lib.scimath',
|
||||
'numpy.lib.stride_tricks',
|
||||
'numpy.random',
|
||||
'numpy.random._pickle',
|
||||
'numpy.random.mtrand',
|
||||
'numpy.random._common',
|
||||
'numpy.random._generator',
|
||||
|
||||
# 编码相关
|
||||
'encodings',
|
||||
'encodings.utf_8',
|
||||
'encodings.gbk',
|
||||
'encodings.cp1252',
|
||||
'encodings.latin1',
|
||||
|
||||
# 其他必要模块
|
||||
'_ctypes',
|
||||
'ctypes.util',
|
||||
'pkg_resources',
|
||||
'pkg_resources.py2_warn',
|
||||
'pkg_resources.markers',
|
||||
|
||||
# Jinja2相关(可选,某些依赖可能需要)
|
||||
# 'jinja2',
|
||||
# 'jinja2.ext',
|
||||
# 'markupsafe',
|
||||
|
||||
# Windows特定模块(仅在Windows上可用)
|
||||
# 'win32api',
|
||||
# 'win32con',
|
||||
# 'pywintypes',
|
||||
# 'pythoncom'
|
||||
],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[
|
||||
# 排除不必要的大型库
|
||||
'matplotlib',
|
||||
'matplotlib.pyplot',
|
||||
'scipy',
|
||||
'sklearn',
|
||||
'tensorflow',
|
||||
'torch',
|
||||
'IPython',
|
||||
'jupyter',
|
||||
'notebook',
|
||||
'tkinter',
|
||||
'PyQt5',
|
||||
'PyQt6',
|
||||
'PySide2',
|
||||
'PySide6',
|
||||
'PIL',
|
||||
'cv2',
|
||||
'seaborn',
|
||||
'plotly',
|
||||
'bokeh',
|
||||
|
||||
# 测试和开发工具
|
||||
'test',
|
||||
'tests',
|
||||
'unittest',
|
||||
'pytest',
|
||||
'setuptools',
|
||||
'pip',
|
||||
'wheel',
|
||||
'distutils',
|
||||
|
||||
# 文档和示例
|
||||
'sphinx',
|
||||
'docutils',
|
||||
'examples',
|
||||
'sample',
|
||||
|
||||
# 其他不需要的模块
|
||||
'curses',
|
||||
'readline',
|
||||
'sqlite3',
|
||||
'xml.etree.ElementTree',
|
||||
'html',
|
||||
'http',
|
||||
'urllib3',
|
||||
'requests'
|
||||
],
|
||||
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)
|
||||
|
||||
# 使用spec文件时,只能使用基本选项
|
||||
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()
|
79
快速打包.bat
Normal file
79
快速打包.bat
Normal file
@@ -0,0 +1,79 @@
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
title 座位分配系统 - 快速打包
|
||||
|
||||
echo.
|
||||
echo ╔══════════════════════════════════════╗
|
||||
echo ║ 座位分配系统 快速打包 ║
|
||||
echo ╚══════════════════════════════════════╝
|
||||
echo.
|
||||
|
||||
:: 检查Python
|
||||
python --version >nul 2>&1
|
||||
if %errorlevel% neq 0 (
|
||||
echo ❌ Python未安装,请先安装Python
|
||||
goto :error
|
||||
)
|
||||
|
||||
:: 安装打包工具
|
||||
echo 📦 安装打包工具...
|
||||
pip install pyinstaller pandas numpy openpyxl --quiet
|
||||
|
||||
:: 清理旧文件
|
||||
echo 🧹 清理旧文件...
|
||||
if exist "dist" rmdir /s /q "dist"
|
||||
if exist "build" rmdir /s /q "build"
|
||||
if exist "release" rmdir /s /q "release"
|
||||
|
||||
:: 开始打包
|
||||
echo 🔨 开始打包...
|
||||
pyinstaller --onefile --console --name "座位分配系统" --clean seat_allocation_system.py
|
||||
|
||||
:: 检查结果
|
||||
if not exist "dist\座位分配系统.exe" (
|
||||
echo ❌ 打包失败
|
||||
goto :error
|
||||
)
|
||||
|
||||
:: 创建发布目录
|
||||
echo 📁 创建发布目录...
|
||||
mkdir "release"
|
||||
copy "dist\座位分配系统.exe" "release\"
|
||||
|
||||
:: 创建说明文件
|
||||
echo 📝 创建说明文件...
|
||||
(
|
||||
echo 座位分配系统 v2.0
|
||||
echo ==================
|
||||
echo.
|
||||
echo 使用方法:
|
||||
echo 1. 将Excel数据文件放在本程序同一目录
|
||||
echo 2. 双击运行"座位分配系统.exe"
|
||||
echo 3. 按提示操作即可
|
||||
echo.
|
||||
echo 输出文件将保存在log文件夹中
|
||||
echo.
|
||||
echo 如有问题请查看log文件夹中的日志
|
||||
) > "release\使用说明.txt"
|
||||
|
||||
:: 完成
|
||||
echo.
|
||||
echo ✅ 打包完成!
|
||||
echo.
|
||||
echo 📍 发布文件位置: release\座位分配系统.exe
|
||||
echo 📍 使用说明: release\使用说明.txt
|
||||
echo.
|
||||
echo 可以将release文件夹分发给用户使用
|
||||
goto :end
|
||||
|
||||
:error
|
||||
echo.
|
||||
echo ❌ 打包失败,请检查错误信息
|
||||
echo.
|
||||
pause
|
||||
exit /b 1
|
||||
|
||||
:end
|
||||
echo.
|
||||
echo 按任意键退出...
|
||||
pause >nul
|
Reference in New Issue
Block a user