项目基本完成
This commit is contained in:
commit
672760bb25
8
.idea/TableSynthesis.iml
Normal file
8
.idea/TableSynthesis.iml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="jdk" jdkName="TableSynthesis" jdkType="Python SDK" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
6
.idea/inspectionProfiles/Project_Default.xml
Normal file
6
.idea/inspectionProfiles/Project_Default.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="PyInterpreterInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
</profile>
|
||||||
|
</component>
|
6
.idea/inspectionProfiles/profiles_settings.xml
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
6
.idea/misc.xml
Normal file
6
.idea/misc.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Black">
|
||||||
|
<option name="sdkName" value="TableSynthesis" />
|
||||||
|
</component>
|
||||||
|
</project>
|
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/TableSynthesis.iml" filepath="$PROJECT_DIR$/.idea/TableSynthesis.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
146
README.md
Normal file
146
README.md
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
# 座位分配系统
|
||||||
|
|
||||||
|
智能座位分配工具,支持1-10人连坐需求,能够处理不连续座位的复杂情况。
|
||||||
|
|
||||||
|
## 🎯 功能特点
|
||||||
|
|
||||||
|
- ✅ **智能分配**: 支持1-10人的各种连坐需求
|
||||||
|
- ✅ **数据校验**: 完整的文件格式和逻辑校验
|
||||||
|
- ✅ **不连续座位**: 自动处理座位号间隙
|
||||||
|
- ✅ **详细日志**: 完整的操作记录和结果验证
|
||||||
|
- ✅ **跨平台**: 支持Windows、macOS、Linux
|
||||||
|
|
||||||
|
## 📋 使用方法
|
||||||
|
|
||||||
|
### 准备Excel文件
|
||||||
|
|
||||||
|
1. **人员信息.xlsx** - 包含以下列:
|
||||||
|
- 姓名、证件类型、证件号、手机号、备注
|
||||||
|
- 备注数字表示连坐人数(如:备注4表示当前行+后3行共4人连坐)
|
||||||
|
|
||||||
|
2. **座位信息.xlsx** - 包含以下列:
|
||||||
|
- 区域、楼层、排号、座位号
|
||||||
|
|
||||||
|
### 运行程序
|
||||||
|
|
||||||
|
#### Python环境运行
|
||||||
|
```bash
|
||||||
|
# 安装依赖
|
||||||
|
pip install pandas openpyxl
|
||||||
|
|
||||||
|
# 运行程序
|
||||||
|
python seat_allocation_system.py
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 构建可执行文件
|
||||||
|
```bash
|
||||||
|
# 在当前平台构建
|
||||||
|
python simple_build.py
|
||||||
|
|
||||||
|
# 跨平台构建(macOS构建Windows版本)
|
||||||
|
python cross_platform_build.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 查看结果
|
||||||
|
|
||||||
|
程序运行后会生成:
|
||||||
|
- `座位信息_最终分配.xlsx` - 最终分配结果
|
||||||
|
- `最终座位分配日志.xlsx` - 详细分配记录
|
||||||
|
- `seat_allocation_log.txt` - 完整运行日志
|
||||||
|
|
||||||
|
## 🔧 构建选项
|
||||||
|
|
||||||
|
### 本地构建
|
||||||
|
```bash
|
||||||
|
# 简单构建(推荐)
|
||||||
|
python simple_build.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 跨平台构建
|
||||||
|
```bash
|
||||||
|
# 在macOS上构建Windows版本
|
||||||
|
python cross_platform_build.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 自动化构建
|
||||||
|
使用GitHub Actions自动构建多平台版本:
|
||||||
|
1. 将 `build_workflow.yml` 放入 `.github/workflows/` 目录
|
||||||
|
2. 推送到GitHub仓库
|
||||||
|
3. 自动构建Windows x86/x64和macOS版本
|
||||||
|
|
||||||
|
## 📁 文件说明
|
||||||
|
|
||||||
|
### 核心文件
|
||||||
|
- `seat_allocation_system.py` - 主程序
|
||||||
|
- `simple_build.py` - 简化构建脚本
|
||||||
|
- `cross_platform_build.py` - 跨平台构建脚本
|
||||||
|
- `requirements.txt` - Python依赖
|
||||||
|
|
||||||
|
### 配置文件
|
||||||
|
- `build_workflow.yml` - GitHub Actions工作流
|
||||||
|
- `人员信息.xlsx` - 示例人员数据
|
||||||
|
- `座位信息.xlsx` - 示例座位数据
|
||||||
|
|
||||||
|
## 🎯 支持的平台
|
||||||
|
|
||||||
|
### 直接运行
|
||||||
|
- Windows 7/8/10/11 (x86/x64/ARM64)
|
||||||
|
- macOS 10.14+ (Intel/Apple Silicon)
|
||||||
|
- Linux (x86_64/ARM64)
|
||||||
|
|
||||||
|
### 构建目标
|
||||||
|
- **Windows x86** - 32位兼容版本
|
||||||
|
- **Windows x64** - 64位主流版本
|
||||||
|
- **macOS** - Intel和Apple Silicon
|
||||||
|
- **Linux** - 主流发行版
|
||||||
|
|
||||||
|
## 📊 数据格式要求
|
||||||
|
|
||||||
|
### 人员信息格式
|
||||||
|
```
|
||||||
|
姓名 | 证件类型 | 证件号 | 手机号 | 备注
|
||||||
|
张三 | 身份证 | 123456789012345678 | 13800138000 | 2
|
||||||
|
李四 | 身份证 | 123456789012345679 | 13800138001 |
|
||||||
|
王五 | 身份证 | 12345678901234567X | 13800138002 | 3
|
||||||
|
赵六 | 身份证 | 123456789012345680 | 13800138003 |
|
||||||
|
钱七 | 身份证 | 123456789012345681 | 13800138004 |
|
||||||
|
```
|
||||||
|
|
||||||
|
### 座位信息格式
|
||||||
|
```
|
||||||
|
区域 | 楼层 | 排号 | 座位号
|
||||||
|
A区通道 | 一层 | 1排 | 1号
|
||||||
|
A区通道 | 一层 | 1排 | 2号
|
||||||
|
A区通道 | 一层 | 1排 | 3号
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚠️ 注意事项
|
||||||
|
|
||||||
|
1. **备注逻辑**: 备注数字表示连坐人数,只有组长填写备注,成员留空
|
||||||
|
2. **证件号格式**: 支持包含X的身份证号,自动处理为字符串格式
|
||||||
|
3. **文字清理**: 自动清除姓名等字段的多余空格
|
||||||
|
4. **座位连续性**: 支持不连续座位号,算法会自动寻找合适的连续段
|
||||||
|
|
||||||
|
## 🔍 故障排除
|
||||||
|
|
||||||
|
### 常见问题
|
||||||
|
1. **文件格式错误**: 确保Excel文件包含必需的列
|
||||||
|
2. **编码问题**: 确保Excel文件使用UTF-8编码
|
||||||
|
3. **权限问题**: 确保有读写Excel文件的权限
|
||||||
|
4. **内存不足**: 处理大量数据时可能需要更多内存
|
||||||
|
|
||||||
|
### 技术支持
|
||||||
|
如遇问题,请查看生成的日志文件:
|
||||||
|
- `seat_allocation_log.txt` - 详细的运行日志
|
||||||
|
- 控制台输出 - 实时处理信息
|
||||||
|
|
||||||
|
## 📈 版本历史
|
||||||
|
|
||||||
|
- **v1.0** - 初始版本,支持基本座位分配
|
||||||
|
- **v1.1** - 添加数据校验和错误处理
|
||||||
|
- **v1.2** - 支持不连续座位处理
|
||||||
|
- **v1.3** - 优化数据类型处理,支持跨平台构建
|
||||||
|
|
||||||
|
## 📄 许可证
|
||||||
|
|
||||||
|
本项目采用MIT许可证,详见LICENSE文件。
|
89
build_workflow.yml
Normal file
89
build_workflow.yml
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
# GitHub Actions工作流 - 多平台构建
|
||||||
|
# 将此文件放在 .github/workflows/ 目录下
|
||||||
|
|
||||||
|
name: Build Multi-Platform Executables
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ main ]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-windows:
|
||||||
|
runs-on: windows-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
arch: [x64, x86]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.10'
|
||||||
|
architecture: ${{ matrix.arch }}
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install pandas openpyxl pyinstaller
|
||||||
|
|
||||||
|
- name: Build executable
|
||||||
|
run: |
|
||||||
|
pyinstaller --onefile --console --name "座位分配系统_${{ matrix.arch }}" seat_allocation_system.py
|
||||||
|
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: windows-${{ matrix.arch }}
|
||||||
|
path: dist/座位分配系统_${{ matrix.arch }}.exe
|
||||||
|
|
||||||
|
build-macos:
|
||||||
|
runs-on: macos-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.10'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install pandas openpyxl pyinstaller
|
||||||
|
|
||||||
|
- name: Build executable
|
||||||
|
run: |
|
||||||
|
pyinstaller --onefile --console --name "座位分配系统_macos" seat_allocation_system.py
|
||||||
|
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: macos
|
||||||
|
path: dist/座位分配系统_macos
|
||||||
|
|
||||||
|
create-release:
|
||||||
|
needs: [build-windows, build-macos]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Download all artifacts
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
|
||||||
|
- name: Create Release
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
with:
|
||||||
|
tag_name: v${{ github.run_number }}
|
||||||
|
name: Release v${{ github.run_number }}
|
||||||
|
files: |
|
||||||
|
windows-x64/座位分配系统_x64.exe
|
||||||
|
windows-x86/座位分配系统_x86.exe
|
||||||
|
macos/座位分配系统_macos
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
326
cross_platform_build.py
Normal file
326
cross_platform_build.py
Normal file
@ -0,0 +1,326 @@
|
|||||||
|
#!/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()
|
898
seat_allocation_system.py
Normal file
898
seat_allocation_system.py
Normal file
@ -0,0 +1,898 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
完整的座位分配系统
|
||||||
|
包含文件校验、智能分配算法和日志输出功能
|
||||||
|
支持1-10人连坐需求,能够处理不连续座位
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
import numpy as np
|
||||||
|
from pathlib import Path
|
||||||
|
import datetime
|
||||||
|
import sys
|
||||||
|
|
||||||
|
class Logger:
|
||||||
|
"""日志记录器"""
|
||||||
|
def __init__(self, log_file='seat_allocation_log.txt'):
|
||||||
|
self.log_file = log_file
|
||||||
|
self.logs = []
|
||||||
|
|
||||||
|
def log(self, message, print_to_console=True):
|
||||||
|
"""记录日志"""
|
||||||
|
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
log_entry = f"[{timestamp}] {message}"
|
||||||
|
self.logs.append(log_entry)
|
||||||
|
|
||||||
|
if print_to_console:
|
||||||
|
print(message)
|
||||||
|
|
||||||
|
def save_logs(self):
|
||||||
|
"""保存日志到文件"""
|
||||||
|
try:
|
||||||
|
with open(self.log_file, 'w', encoding='utf-8') as f:
|
||||||
|
f.write('\n'.join(self.logs))
|
||||||
|
self.log(f"日志已保存到: {self.log_file}")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"保存日志失败: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
class SeatAllocationSystem:
|
||||||
|
"""座位分配系统"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.logger = Logger()
|
||||||
|
self.personnel_df = None
|
||||||
|
self.seat_df = None
|
||||||
|
self.seating_groups = []
|
||||||
|
self.row_analysis = {}
|
||||||
|
|
||||||
|
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} 文件不存在")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not Path(seat_file).exists():
|
||||||
|
self.logger.log(f"❌ 错误: {seat_file} 文件不存在")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 读取人员信息,指定数据类型
|
||||||
|
self.personnel_df = pd.read_excel(personnel_file, dtype={
|
||||||
|
'姓名': 'str',
|
||||||
|
'证件类型': 'str',
|
||||||
|
'证件号': 'str', # 证件号作为字符串读取,避免X被转换
|
||||||
|
'手机号': 'str'
|
||||||
|
})
|
||||||
|
|
||||||
|
# 读取座位信息,指定数据类型
|
||||||
|
self.seat_df = pd.read_excel(seat_file, dtype={
|
||||||
|
'区域': 'str',
|
||||||
|
'楼层': 'str',
|
||||||
|
'排号': 'str',
|
||||||
|
'座位号': 'str',
|
||||||
|
'姓名': 'str',
|
||||||
|
'证件类型': 'str',
|
||||||
|
'证件号': 'str',
|
||||||
|
'手机号': 'str',
|
||||||
|
'签发地/国籍': 'str'
|
||||||
|
})
|
||||||
|
|
||||||
|
# 清理文字信息中的空格
|
||||||
|
self.clean_text_data()
|
||||||
|
|
||||||
|
self.logger.log(f"✅ 文件加载成功")
|
||||||
|
self.logger.log(f" 人员信息: {self.personnel_df.shape[0]} 行 × {self.personnel_df.shape[1]} 列")
|
||||||
|
self.logger.log(f" 座位信息: {self.seat_df.shape[0]} 行 × {self.seat_df.shape[1]} 列")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.log(f"❌ 文件加载失败: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def clean_text_data(self):
|
||||||
|
"""清理文字数据中的空格"""
|
||||||
|
self.logger.log("清理文字数据中的空格...")
|
||||||
|
|
||||||
|
# 清理人员信息中的空格
|
||||||
|
text_columns_personnel = ['姓名', '证件类型', '证件号']
|
||||||
|
for col in text_columns_personnel:
|
||||||
|
if col in self.personnel_df.columns:
|
||||||
|
self.personnel_df[col] = self.personnel_df[col].astype(str).str.strip()
|
||||||
|
|
||||||
|
# 清理座位信息中的空格
|
||||||
|
text_columns_seat = ['区域', '楼层', '排号', '座位号', '姓名', '证件类型', '证件号', '签发地/国籍']
|
||||||
|
for col in text_columns_seat:
|
||||||
|
if col in self.seat_df.columns:
|
||||||
|
# 只对非空值进行清理
|
||||||
|
mask = self.seat_df[col].notna()
|
||||||
|
self.seat_df.loc[mask, col] = self.seat_df.loc[mask, col].astype(str).str.strip()
|
||||||
|
|
||||||
|
self.logger.log("✅ 文字数据清理完成")
|
||||||
|
|
||||||
|
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 ['姓名', '证件类型', '证件号', '手机号']:
|
||||||
|
if col in self.personnel_df.columns:
|
||||||
|
null_count = self.personnel_df[col].isnull().sum()
|
||||||
|
if null_count > 0:
|
||||||
|
self.logger.log(f"⚠️ {col} 列有 {null_count} 个空值")
|
||||||
|
validation_results.append(False)
|
||||||
|
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)
|
||||||
|
else:
|
||||||
|
self.logger.log("✅ 姓名无重复")
|
||||||
|
validation_results.append(True)
|
||||||
|
|
||||||
|
return all(validation_results)
|
||||||
|
|
||||||
|
def validate_seating_groups(self):
|
||||||
|
"""校验连坐组的完整性和正确性"""
|
||||||
|
self.logger.log("\n=== 连坐组校验 ===")
|
||||||
|
|
||||||
|
validation_results = []
|
||||||
|
issues = []
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
group_num = 1
|
||||||
|
|
||||||
|
while i < len(self.personnel_df):
|
||||||
|
person = self.personnel_df.iloc[i]
|
||||||
|
remark = person['备注']
|
||||||
|
|
||||||
|
if pd.isna(remark):
|
||||||
|
# 无备注,单独坐
|
||||||
|
self.logger.log(f"✅ 第 {group_num} 组: {person['姓名']} (单独)")
|
||||||
|
i += 1
|
||||||
|
else:
|
||||||
|
# 有备注,检查连坐组
|
||||||
|
try:
|
||||||
|
group_size = int(remark)
|
||||||
|
|
||||||
|
if group_size < 1 or group_size > 10:
|
||||||
|
issue = f"❌ 第 {group_num} 组: {person['姓名']} 的备注 {group_size} 超出范围 (1-10)"
|
||||||
|
self.logger.log(issue)
|
||||||
|
issues.append(issue)
|
||||||
|
validation_results.append(False)
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 检查后续人员是否足够
|
||||||
|
if i + group_size > len(self.personnel_df):
|
||||||
|
issue = f"❌ 第 {group_num} 组: {person['姓名']} 需要 {group_size} 人连坐,但后续人员不足"
|
||||||
|
self.logger.log(issue)
|
||||||
|
issues.append(issue)
|
||||||
|
validation_results.append(False)
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 检查后续人员的备注是否为空
|
||||||
|
group_members = []
|
||||||
|
valid_group = True
|
||||||
|
|
||||||
|
for j in range(group_size):
|
||||||
|
member = self.personnel_df.iloc[i + j]
|
||||||
|
group_members.append(member['姓名'])
|
||||||
|
|
||||||
|
if j == 0:
|
||||||
|
# 第一个人应该有备注
|
||||||
|
if pd.isna(member['备注']) or int(member['备注']) != group_size:
|
||||||
|
issue = f"❌ 第 {group_num} 组: 组长 {member['姓名']} 的备注应该是 {group_size}"
|
||||||
|
self.logger.log(issue)
|
||||||
|
issues.append(issue)
|
||||||
|
valid_group = False
|
||||||
|
else:
|
||||||
|
# 后续人员应该没有备注
|
||||||
|
if not pd.isna(member['备注']):
|
||||||
|
issue = f"❌ 第 {group_num} 组: {member['姓名']} 应该没有备注(当前备注: {member['备注']})"
|
||||||
|
self.logger.log(issue)
|
||||||
|
issues.append(issue)
|
||||||
|
valid_group = False
|
||||||
|
|
||||||
|
if valid_group:
|
||||||
|
self.logger.log(f"✅ 第 {group_num} 组: {', '.join(group_members)} ({group_size}人连坐)")
|
||||||
|
validation_results.append(True)
|
||||||
|
else:
|
||||||
|
validation_results.append(False)
|
||||||
|
|
||||||
|
i += group_size
|
||||||
|
|
||||||
|
except ValueError:
|
||||||
|
issue = f"❌ 第 {group_num} 组: {person['姓名']} 的备注 '{remark}' 不是有效数字"
|
||||||
|
self.logger.log(issue)
|
||||||
|
issues.append(issue)
|
||||||
|
validation_results.append(False)
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
group_num += 1
|
||||||
|
|
||||||
|
return all(validation_results), issues
|
||||||
|
|
||||||
|
def validate_seat_structure(self):
|
||||||
|
"""校验座位信息结构和连续性"""
|
||||||
|
self.logger.log("\n=== 座位信息结构校验 ===")
|
||||||
|
|
||||||
|
required_columns = ['区域', '楼层', '排号', '座位号']
|
||||||
|
validation_results = []
|
||||||
|
|
||||||
|
# 检查必需列
|
||||||
|
missing_columns = []
|
||||||
|
for col in required_columns:
|
||||||
|
if col not in self.seat_df.columns:
|
||||||
|
missing_columns.append(col)
|
||||||
|
|
||||||
|
if missing_columns:
|
||||||
|
self.logger.log(f"❌ 缺少必需列: {missing_columns}")
|
||||||
|
return False, {}
|
||||||
|
|
||||||
|
self.logger.log("✅ 所有必需列都存在")
|
||||||
|
|
||||||
|
# 检查数据完整性
|
||||||
|
for col in required_columns:
|
||||||
|
null_count = self.seat_df[col].isnull().sum()
|
||||||
|
if null_count > 0:
|
||||||
|
self.logger.log(f"❌ {col} 列有 {null_count} 个空值")
|
||||||
|
validation_results.append(False)
|
||||||
|
else:
|
||||||
|
self.logger.log(f"✅ {col} 列数据完整")
|
||||||
|
validation_results.append(True)
|
||||||
|
|
||||||
|
# 分析座位结构
|
||||||
|
self.logger.log("\n座位结构分析:")
|
||||||
|
seat_groups = {}
|
||||||
|
for _, row in self.seat_df.iterrows():
|
||||||
|
key = (row['区域'], row['楼层'], row['排号'])
|
||||||
|
if key not in seat_groups:
|
||||||
|
seat_groups[key] = []
|
||||||
|
seat_groups[key].append(row['座位号'])
|
||||||
|
|
||||||
|
# 检查每排的座位结构和可用连续段
|
||||||
|
self.logger.log("\n座位结构分析:")
|
||||||
|
for (area, floor, row_num), seats in seat_groups.items():
|
||||||
|
# 提取座位号数字
|
||||||
|
seat_numbers = []
|
||||||
|
for seat in seats:
|
||||||
|
try:
|
||||||
|
seat_numbers.append(int(str(seat).replace('号', '')))
|
||||||
|
except:
|
||||||
|
self.logger.log(f"❌ {area}-{floor}-{row_num}: 座位号格式异常 '{seat}'")
|
||||||
|
validation_results.append(False)
|
||||||
|
continue
|
||||||
|
|
||||||
|
seat_numbers.sort()
|
||||||
|
|
||||||
|
# 分析连续段
|
||||||
|
consecutive_segments = []
|
||||||
|
if seat_numbers:
|
||||||
|
current_segment = [seat_numbers[0]]
|
||||||
|
|
||||||
|
for i in range(1, len(seat_numbers)):
|
||||||
|
if seat_numbers[i] - seat_numbers[i-1] == 1:
|
||||||
|
# 连续
|
||||||
|
current_segment.append(seat_numbers[i])
|
||||||
|
else:
|
||||||
|
# 不连续,保存当前段,开始新段
|
||||||
|
consecutive_segments.append(current_segment)
|
||||||
|
current_segment = [seat_numbers[i]]
|
||||||
|
|
||||||
|
# 添加最后一段
|
||||||
|
consecutive_segments.append(current_segment)
|
||||||
|
|
||||||
|
# 显示分析结果
|
||||||
|
if len(consecutive_segments) == 1 and len(consecutive_segments[0]) == len(seat_numbers):
|
||||||
|
self.logger.log(f"✅ {area}-{floor}-{row_num}: {len(seats)} 个座位完全连续 ({min(seat_numbers)}-{max(seat_numbers)})")
|
||||||
|
else:
|
||||||
|
segments_info = []
|
||||||
|
max_segment_size = 0
|
||||||
|
for segment in consecutive_segments:
|
||||||
|
if len(segment) == 1:
|
||||||
|
segments_info.append(f"{segment[0]}")
|
||||||
|
else:
|
||||||
|
segments_info.append(f"{segment[0]}-{segment[-1]}")
|
||||||
|
max_segment_size = max(max_segment_size, len(segment))
|
||||||
|
|
||||||
|
self.logger.log(f"📊 {area}-{floor}-{row_num}: {len(seats)} 个座位,{len(consecutive_segments)} 个连续段: {', '.join(segments_info)}")
|
||||||
|
self.logger.log(f" 最大连续段: {max_segment_size} 个座位")
|
||||||
|
|
||||||
|
validation_results.append(True) # 座位不连续不算错误,只是需要特殊处理
|
||||||
|
|
||||||
|
return all(validation_results), seat_groups
|
||||||
|
|
||||||
|
def validate_capacity_feasibility(self, seat_groups):
|
||||||
|
"""校验座位容量和分配可行性"""
|
||||||
|
self.logger.log("\n=== 容量和可行性校验 ===")
|
||||||
|
|
||||||
|
# 统计人员总数
|
||||||
|
total_people = len(self.personnel_df)
|
||||||
|
total_seats = sum(len(seats) for seats in seat_groups.values())
|
||||||
|
|
||||||
|
self.logger.log(f"总人数: {total_people}")
|
||||||
|
self.logger.log(f"总座位数: {total_seats}")
|
||||||
|
|
||||||
|
if total_people > total_seats:
|
||||||
|
self.logger.log(f"❌ 座位不足: 需要 {total_people} 个座位,只有 {total_seats} 个")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
self.logger.log(f"✅ 座位充足: 剩余 {total_seats - total_people} 个座位")
|
||||||
|
|
||||||
|
# 分析连坐组需求
|
||||||
|
self.logger.log("\n连坐组需求分析:")
|
||||||
|
group_sizes = []
|
||||||
|
i = 0
|
||||||
|
|
||||||
|
while i < len(self.personnel_df):
|
||||||
|
person = self.personnel_df.iloc[i]
|
||||||
|
remark = person['备注']
|
||||||
|
|
||||||
|
if pd.isna(remark):
|
||||||
|
group_sizes.append(1)
|
||||||
|
i += 1
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
group_size = int(remark)
|
||||||
|
group_sizes.append(group_size)
|
||||||
|
i += group_size
|
||||||
|
except:
|
||||||
|
group_sizes.append(1)
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
max_group_size = max(group_sizes)
|
||||||
|
self.logger.log(f"最大连坐组: {max_group_size} 人")
|
||||||
|
|
||||||
|
# 检查是否有足够的连续座位来容纳最大连坐组
|
||||||
|
self.logger.log(f"\n连续座位可行性分析:")
|
||||||
|
max_consecutive_available = 0
|
||||||
|
feasible_rows = []
|
||||||
|
|
||||||
|
for (area, floor, row_num), seats in seat_groups.items():
|
||||||
|
# 分析每排的连续座位段
|
||||||
|
seat_numbers = []
|
||||||
|
for seat in seats:
|
||||||
|
try:
|
||||||
|
seat_numbers.append(int(str(seat).replace('号', '')))
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
|
||||||
|
seat_numbers.sort()
|
||||||
|
|
||||||
|
# 找出最大连续段
|
||||||
|
max_consecutive_in_row = 0
|
||||||
|
if seat_numbers:
|
||||||
|
current_consecutive = 1
|
||||||
|
for i in range(1, len(seat_numbers)):
|
||||||
|
if seat_numbers[i] - seat_numbers[i-1] == 1:
|
||||||
|
current_consecutive += 1
|
||||||
|
else:
|
||||||
|
max_consecutive_in_row = max(max_consecutive_in_row, current_consecutive)
|
||||||
|
current_consecutive = 1
|
||||||
|
max_consecutive_in_row = max(max_consecutive_in_row, current_consecutive)
|
||||||
|
|
||||||
|
if max_consecutive_in_row >= max_group_size:
|
||||||
|
feasible_rows.append((area, floor, row_num, max_consecutive_in_row))
|
||||||
|
|
||||||
|
max_consecutive_available = max(max_consecutive_available, max_consecutive_in_row)
|
||||||
|
self.logger.log(f" {area}-{floor}-{row_num}: 最大连续 {max_consecutive_in_row} 个座位")
|
||||||
|
|
||||||
|
self.logger.log(f"\n全场最大连续座位: {max_consecutive_available} 个")
|
||||||
|
|
||||||
|
if max_group_size > max_consecutive_available:
|
||||||
|
self.logger.log(f"❌ 无法容纳最大连坐组: 需要 {max_group_size} 个连续座位,最大连续段只有 {max_consecutive_available} 个")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
self.logger.log(f"✅ 可以容纳最大连坐组")
|
||||||
|
self.logger.log(f"可容纳最大连坐组的排数: {len(feasible_rows)} 个")
|
||||||
|
|
||||||
|
# 统计各种大小的连坐组
|
||||||
|
size_counts = {}
|
||||||
|
for size in group_sizes:
|
||||||
|
size_counts[size] = size_counts.get(size, 0) + 1
|
||||||
|
|
||||||
|
self.logger.log("\n连坐组分布:")
|
||||||
|
for size in sorted(size_counts.keys()):
|
||||||
|
count = size_counts[size]
|
||||||
|
if size == 1:
|
||||||
|
self.logger.log(f" 单人组: {count} 个")
|
||||||
|
else:
|
||||||
|
self.logger.log(f" {size}人连坐组: {count} 个")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def analyze_seating_requirements(self):
|
||||||
|
"""分析人员连坐需求"""
|
||||||
|
self.logger.log("\n=== 人员连坐需求分析 ===")
|
||||||
|
|
||||||
|
self.seating_groups = []
|
||||||
|
i = 0
|
||||||
|
|
||||||
|
while i < len(self.personnel_df):
|
||||||
|
person = self.personnel_df.iloc[i]
|
||||||
|
remark = person['备注']
|
||||||
|
|
||||||
|
if pd.isna(remark):
|
||||||
|
# 无备注,单独坐
|
||||||
|
self.seating_groups.append({
|
||||||
|
'type': 'single',
|
||||||
|
'size': 1,
|
||||||
|
'members': [person],
|
||||||
|
'leader': person['姓名']
|
||||||
|
})
|
||||||
|
i += 1
|
||||||
|
else:
|
||||||
|
# 有备注,按备注数量连坐
|
||||||
|
group_size = int(remark)
|
||||||
|
group_members = []
|
||||||
|
|
||||||
|
# 收集连坐组成员
|
||||||
|
for j in range(group_size):
|
||||||
|
if i + j < len(self.personnel_df):
|
||||||
|
group_members.append(self.personnel_df.iloc[i + j])
|
||||||
|
|
||||||
|
self.seating_groups.append({
|
||||||
|
'type': 'group' if group_size > 1 else 'single',
|
||||||
|
'size': len(group_members),
|
||||||
|
'members': group_members,
|
||||||
|
'leader': person['姓名']
|
||||||
|
})
|
||||||
|
|
||||||
|
i += 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"总共识别出 {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_seat_structure_advanced(self):
|
||||||
|
"""高级座位结构分析,识别连续段"""
|
||||||
|
self.logger.log("\n=== 高级座位结构分析 ===")
|
||||||
|
|
||||||
|
seat_groups = {}
|
||||||
|
for _, row in self.seat_df.iterrows():
|
||||||
|
key = (row['区域'], row['楼层'], row['排号'])
|
||||||
|
if key not in seat_groups:
|
||||||
|
seat_groups[key] = []
|
||||||
|
seat_groups[key].append({
|
||||||
|
'index': row.name,
|
||||||
|
'座位号': row['座位号'],
|
||||||
|
'row_data': row
|
||||||
|
})
|
||||||
|
|
||||||
|
# 分析每排的连续段
|
||||||
|
self.row_analysis = {}
|
||||||
|
for key, seats in seat_groups.items():
|
||||||
|
area, floor, row_num = key
|
||||||
|
|
||||||
|
# 按座位号排序
|
||||||
|
def get_seat_number(seat_info):
|
||||||
|
try:
|
||||||
|
return int(seat_info['座位号'].replace('号', ''))
|
||||||
|
except:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
seats.sort(key=get_seat_number)
|
||||||
|
seat_numbers = [get_seat_number(seat) for seat in seats]
|
||||||
|
|
||||||
|
# 找出所有连续段
|
||||||
|
consecutive_segments = []
|
||||||
|
if seat_numbers:
|
||||||
|
current_segment_start = 0
|
||||||
|
|
||||||
|
for i in range(1, len(seat_numbers)):
|
||||||
|
if seat_numbers[i] - seat_numbers[i-1] != 1:
|
||||||
|
# 发现间隙,结束当前段
|
||||||
|
consecutive_segments.append({
|
||||||
|
'start_idx': current_segment_start,
|
||||||
|
'end_idx': i - 1,
|
||||||
|
'size': i - current_segment_start,
|
||||||
|
'seat_numbers': seat_numbers[current_segment_start:i],
|
||||||
|
'seats': seats[current_segment_start:i]
|
||||||
|
})
|
||||||
|
current_segment_start = i
|
||||||
|
|
||||||
|
# 添加最后一段
|
||||||
|
consecutive_segments.append({
|
||||||
|
'start_idx': current_segment_start,
|
||||||
|
'end_idx': len(seat_numbers) - 1,
|
||||||
|
'size': len(seat_numbers) - current_segment_start,
|
||||||
|
'seat_numbers': seat_numbers[current_segment_start:],
|
||||||
|
'seats': seats[current_segment_start:]
|
||||||
|
})
|
||||||
|
|
||||||
|
self.row_analysis[key] = {
|
||||||
|
'total_seats': len(seats),
|
||||||
|
'all_seats': seats,
|
||||||
|
'consecutive_segments': consecutive_segments,
|
||||||
|
'max_consecutive': max(seg['size'] for seg in consecutive_segments) if consecutive_segments else 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# 显示分析结果
|
||||||
|
if len(consecutive_segments) == 1:
|
||||||
|
self.logger.log(f"✅ {area}-{floor}-{row_num}: {len(seats)} 个座位完全连续")
|
||||||
|
else:
|
||||||
|
segments_info = []
|
||||||
|
for seg in consecutive_segments:
|
||||||
|
if seg['size'] == 1:
|
||||||
|
segments_info.append(f"{seg['seat_numbers'][0]}")
|
||||||
|
else:
|
||||||
|
segments_info.append(f"{seg['seat_numbers'][0]}-{seg['seat_numbers'][-1]}")
|
||||||
|
self.logger.log(f"📊 {area}-{floor}-{row_num}: {len(seats)} 个座位,{len(consecutive_segments)} 段: {', '.join(segments_info)}")
|
||||||
|
self.logger.log(f" 最大连续段: {self.row_analysis[key]['max_consecutive']} 个座位")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def smart_seat_assignment(self):
|
||||||
|
"""智能座位分配算法"""
|
||||||
|
self.logger.log("\n=== 开始智能座位分配 ===")
|
||||||
|
|
||||||
|
# 按组大小排序,大组优先分配
|
||||||
|
sorted_groups = sorted(self.seating_groups, key=lambda x: x['size'], reverse=True)
|
||||||
|
|
||||||
|
# 创建座位分配结果
|
||||||
|
seat_df_copy = self.seat_df.copy()
|
||||||
|
|
||||||
|
# 记录已使用的座位
|
||||||
|
used_seats = set()
|
||||||
|
|
||||||
|
assignment_log = []
|
||||||
|
unassigned_groups = []
|
||||||
|
|
||||||
|
self.logger.log(f"需要分配 {len(sorted_groups)} 个组")
|
||||||
|
|
||||||
|
for group_idx, seating_group in enumerate(sorted_groups):
|
||||||
|
group_size = seating_group['size']
|
||||||
|
group_type = seating_group['type']
|
||||||
|
leader = seating_group['leader']
|
||||||
|
|
||||||
|
self.logger.log(f"\n处理第 {group_idx + 1} 组: {leader} ({group_type}, {group_size} 人)")
|
||||||
|
|
||||||
|
if group_size == 1:
|
||||||
|
# 单人组,找任意可用座位
|
||||||
|
assigned = False
|
||||||
|
for (area, floor, row_num), analysis in self.row_analysis.items():
|
||||||
|
for seat_info in analysis['all_seats']:
|
||||||
|
if seat_info['index'] not in used_seats:
|
||||||
|
# 分配座位
|
||||||
|
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()
|
||||||
|
|
||||||
|
assignment_log.append({
|
||||||
|
'组号': group_idx + 1,
|
||||||
|
'组类型': group_type,
|
||||||
|
'组大小': group_size,
|
||||||
|
'组长': leader,
|
||||||
|
'姓名': person_info['姓名'],
|
||||||
|
'区域': area,
|
||||||
|
'楼层': floor,
|
||||||
|
'排号': row_num,
|
||||||
|
'座位号': seat_info['座位号']
|
||||||
|
})
|
||||||
|
|
||||||
|
used_seats.add(seat_index)
|
||||||
|
self.logger.log(f" 分配到 {area}-{floor}-{row_num}: {person_info['姓名']} -> {seat_info['座位号']}")
|
||||||
|
assigned = True
|
||||||
|
break
|
||||||
|
if assigned:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# 多人组,需要连续座位
|
||||||
|
# 更新可用座位分析(排除已使用的座位)
|
||||||
|
current_row_analysis = {}
|
||||||
|
for key, analysis in self.row_analysis.items():
|
||||||
|
available_seats = [seat for seat in analysis['all_seats'] if seat['index'] not in used_seats]
|
||||||
|
if available_seats:
|
||||||
|
# 重新分析连续段
|
||||||
|
available_seats.sort(key=lambda x: int(x['座位号'].replace('号', '')))
|
||||||
|
seat_numbers = [int(seat['座位号'].replace('号', '')) for seat in available_seats]
|
||||||
|
|
||||||
|
# 找出连续段
|
||||||
|
consecutive_segments = []
|
||||||
|
if seat_numbers:
|
||||||
|
current_segment_start = 0
|
||||||
|
|
||||||
|
for i in range(1, len(seat_numbers)):
|
||||||
|
if seat_numbers[i] - seat_numbers[i-1] != 1:
|
||||||
|
consecutive_segments.append({
|
||||||
|
'start_idx': current_segment_start,
|
||||||
|
'end_idx': i - 1,
|
||||||
|
'size': i - current_segment_start,
|
||||||
|
'seats': available_seats[current_segment_start:i]
|
||||||
|
})
|
||||||
|
current_segment_start = i
|
||||||
|
|
||||||
|
consecutive_segments.append({
|
||||||
|
'start_idx': current_segment_start,
|
||||||
|
'end_idx': len(seat_numbers) - 1,
|
||||||
|
'size': len(seat_numbers) - current_segment_start,
|
||||||
|
'seats': available_seats[current_segment_start:]
|
||||||
|
})
|
||||||
|
|
||||||
|
current_row_analysis[key] = {
|
||||||
|
'consecutive_segments': consecutive_segments
|
||||||
|
}
|
||||||
|
|
||||||
|
# 寻找合适的连续座位
|
||||||
|
assigned = False
|
||||||
|
for (area, floor, row_num), analysis in current_row_analysis.items():
|
||||||
|
for segment in analysis['consecutive_segments']:
|
||||||
|
if segment['size'] >= group_size:
|
||||||
|
# 找到合适的连续座位
|
||||||
|
seats_to_use = segment['seats'][:group_size]
|
||||||
|
seat_numbers = [int(seat['座位号'].replace('号', '')) for seat in seats_to_use]
|
||||||
|
|
||||||
|
self.logger.log(f" 分配到 {area}-{floor}-{row_num} (连续座位: {min(seat_numbers)}-{max(seat_numbers)})")
|
||||||
|
|
||||||
|
# 分配座位
|
||||||
|
for i, seat_info in enumerate(seats_to_use):
|
||||||
|
person_info = seating_group['members'][i]
|
||||||
|
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()
|
||||||
|
|
||||||
|
assignment_log.append({
|
||||||
|
'组号': group_idx + 1,
|
||||||
|
'组类型': group_type,
|
||||||
|
'组大小': group_size,
|
||||||
|
'组长': leader,
|
||||||
|
'姓名': person_info['姓名'],
|
||||||
|
'区域': area,
|
||||||
|
'楼层': floor,
|
||||||
|
'排号': row_num,
|
||||||
|
'座位号': seat_info['座位号']
|
||||||
|
})
|
||||||
|
|
||||||
|
used_seats.add(seat_index)
|
||||||
|
self.logger.log(f" {person_info['姓名']} -> {seat_info['座位号']}")
|
||||||
|
|
||||||
|
assigned = True
|
||||||
|
break
|
||||||
|
if assigned:
|
||||||
|
break
|
||||||
|
|
||||||
|
if not assigned:
|
||||||
|
self.logger.log(f" ❌ 无法为第 {group_idx + 1} 组分配座位")
|
||||||
|
unassigned_groups.append(seating_group)
|
||||||
|
|
||||||
|
# 显示未分配的组
|
||||||
|
if unassigned_groups:
|
||||||
|
self.logger.log(f"\n⚠️ 有 {len(unassigned_groups)} 个组未能分配座位")
|
||||||
|
for group in unassigned_groups:
|
||||||
|
if group['type'] == 'single':
|
||||||
|
self.logger.log(f" 未分配: {group['leader']}")
|
||||||
|
else:
|
||||||
|
member_names = [member['姓名'] for member in group['members']]
|
||||||
|
self.logger.log(f" 未分配: {', '.join(member_names)} (连坐 {group['size']} 人)")
|
||||||
|
|
||||||
|
return seat_df_copy, assignment_log
|
||||||
|
|
||||||
|
def save_results(self, seat_df_result, assignment_log):
|
||||||
|
"""保存分配结果"""
|
||||||
|
try:
|
||||||
|
# 保存更新后的座位信息
|
||||||
|
output_file = '座位信息_最终分配.xlsx'
|
||||||
|
seat_df_result.to_excel(output_file, index=False)
|
||||||
|
self.logger.log(f"\n座位分配结果已保存到: {output_file}")
|
||||||
|
|
||||||
|
# 保存分配日志
|
||||||
|
if assignment_log:
|
||||||
|
log_df = pd.DataFrame(assignment_log)
|
||||||
|
log_file = '最终座位分配日志.xlsx'
|
||||||
|
log_df.to_excel(log_file, index=False)
|
||||||
|
self.logger.log(f"分配日志已保存到: {log_file}")
|
||||||
|
|
||||||
|
# 显示分配统计
|
||||||
|
self.logger.log(f"\n=== 分配统计 ===")
|
||||||
|
self.logger.log(f"总共分配了 {len(assignment_log)} 个座位")
|
||||||
|
|
||||||
|
# 按组大小统计
|
||||||
|
size_stats = log_df.groupby('组大小').agg({
|
||||||
|
'姓名': 'count',
|
||||||
|
'组号': 'nunique'
|
||||||
|
}).rename(columns={'姓名': '总人数', '组号': '组数'})
|
||||||
|
|
||||||
|
self.logger.log("\n按组大小统计:")
|
||||||
|
for size, row in size_stats.iterrows():
|
||||||
|
if size == 1:
|
||||||
|
self.logger.log(f" 单人组: {row['组数']} 个组, {row['总人数']} 人")
|
||||||
|
else:
|
||||||
|
self.logger.log(f" {size}人连坐组: {row['组数']} 个组, {row['总人数']} 人")
|
||||||
|
|
||||||
|
# 验证连续性
|
||||||
|
self.logger.log("\n=== 连续性验证 ===")
|
||||||
|
consecutive_check = []
|
||||||
|
|
||||||
|
for group_num in sorted(log_df['组号'].unique()):
|
||||||
|
group_data = log_df[log_df['组号'] == group_num]
|
||||||
|
group_size = group_data.iloc[0]['组大小']
|
||||||
|
group_leader = group_data.iloc[0]['组长']
|
||||||
|
|
||||||
|
if group_size > 1: # 多人组
|
||||||
|
# 检查是否在同一排
|
||||||
|
areas = group_data['区域'].unique()
|
||||||
|
floors = group_data['楼层'].unique()
|
||||||
|
rows = group_data['排号'].unique()
|
||||||
|
|
||||||
|
if len(areas) == 1 and len(floors) == 1 and len(rows) == 1:
|
||||||
|
# 在同一排,检查座位号是否连续
|
||||||
|
seats = group_data['座位号'].tolist()
|
||||||
|
seat_numbers = []
|
||||||
|
for seat in seats:
|
||||||
|
try:
|
||||||
|
seat_numbers.append(int(seat.replace('号', '')))
|
||||||
|
except:
|
||||||
|
seat_numbers.append(0)
|
||||||
|
|
||||||
|
seat_numbers.sort()
|
||||||
|
is_consecutive = all(seat_numbers[i] + 1 == seat_numbers[i+1] for i in range(len(seat_numbers)-1))
|
||||||
|
|
||||||
|
if is_consecutive:
|
||||||
|
consecutive_check.append(True)
|
||||||
|
self.logger.log(f"✅ 组 {group_num} ({group_leader}): {group_size}人连坐,座位连续 {seat_numbers}")
|
||||||
|
else:
|
||||||
|
consecutive_check.append(False)
|
||||||
|
self.logger.log(f"❌ 组 {group_num} ({group_leader}): {group_size}人连坐,座位不连续 {seat_numbers}")
|
||||||
|
else:
|
||||||
|
consecutive_check.append(False)
|
||||||
|
self.logger.log(f"❌ 组 {group_num} ({group_leader}): 不在同一排")
|
||||||
|
|
||||||
|
success_rate = sum(consecutive_check) / len(consecutive_check) * 100 if consecutive_check else 100
|
||||||
|
self.logger.log(f"\n连续性检查结果: {sum(consecutive_check)}/{len(consecutive_check)} 个多人组座位连续 ({success_rate:.1f}%)")
|
||||||
|
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.log(f"保存结果时出错: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def run_validation(self):
|
||||||
|
"""运行完整的文件校验"""
|
||||||
|
self.logger.log("=" * 60)
|
||||||
|
self.logger.log("座位分配系统 - 文件校验")
|
||||||
|
self.logger.log("=" * 60)
|
||||||
|
|
||||||
|
# 加载数据
|
||||||
|
if not self.load_data():
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 人员信息结构校验
|
||||||
|
personnel_structure_valid = self.validate_personnel_structure()
|
||||||
|
|
||||||
|
# 连坐组校验
|
||||||
|
seating_groups_valid, group_issues = self.validate_seating_groups()
|
||||||
|
|
||||||
|
# 座位信息校验
|
||||||
|
seat_structure_valid, seat_groups = self.validate_seat_structure()
|
||||||
|
|
||||||
|
# 容量可行性校验
|
||||||
|
capacity_valid = self.validate_capacity_feasibility(seat_groups)
|
||||||
|
|
||||||
|
# 总结
|
||||||
|
self.logger.log("\n" + "=" * 60)
|
||||||
|
self.logger.log("校验结果总结")
|
||||||
|
self.logger.log("=" * 60)
|
||||||
|
|
||||||
|
all_valid = all([
|
||||||
|
personnel_structure_valid,
|
||||||
|
seating_groups_valid,
|
||||||
|
seat_structure_valid,
|
||||||
|
capacity_valid
|
||||||
|
])
|
||||||
|
|
||||||
|
self.logger.log(f"人员信息结构: {'✅ 通过' if personnel_structure_valid else '❌ 失败'}")
|
||||||
|
self.logger.log(f"连坐组完整性: {'✅ 通过' if seating_groups_valid else '❌ 失败'}")
|
||||||
|
self.logger.log(f"座位信息结构: {'✅ 通过' if seat_structure_valid else '❌ 失败'}")
|
||||||
|
self.logger.log(f"容量可行性: {'✅ 通过' if capacity_valid else '❌ 失败'}")
|
||||||
|
|
||||||
|
self.logger.log(f"\n总体校验结果: {'✅ 全部通过' if all_valid else '❌ 存在问题'}")
|
||||||
|
|
||||||
|
if group_issues:
|
||||||
|
self.logger.log(f"\n发现的问题:")
|
||||||
|
for issue in group_issues:
|
||||||
|
self.logger.log(f" {issue}")
|
||||||
|
|
||||||
|
if all_valid:
|
||||||
|
self.logger.log("\n🎉 文件校验通过,可以进行座位分配!")
|
||||||
|
else:
|
||||||
|
self.logger.log("\n⚠️ 请修复上述问题后再进行座位分配。")
|
||||||
|
|
||||||
|
return all_valid
|
||||||
|
|
||||||
|
def run_allocation(self):
|
||||||
|
"""运行完整的座位分配"""
|
||||||
|
self.logger.log("\n" + "=" * 60)
|
||||||
|
self.logger.log("开始座位分配")
|
||||||
|
self.logger.log("=" * 60)
|
||||||
|
|
||||||
|
# 分析人员连坐需求
|
||||||
|
if not self.analyze_seating_requirements():
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 高级座位结构分析
|
||||||
|
if not self.analyze_seat_structure_advanced():
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 执行智能座位分配
|
||||||
|
seat_df_result, assignment_log = self.smart_seat_assignment()
|
||||||
|
|
||||||
|
# 保存结果
|
||||||
|
if self.save_results(seat_df_result, assignment_log):
|
||||||
|
self.logger.log("\n🎉 座位分配完成!")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
self.logger.log("\n❌ 座位分配失败!")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主函数"""
|
||||||
|
system = SeatAllocationSystem()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 运行校验
|
||||||
|
if system.run_validation():
|
||||||
|
# 校验通过,运行分配
|
||||||
|
system.run_allocation()
|
||||||
|
|
||||||
|
# 保存日志
|
||||||
|
system.logger.save_logs()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
system.logger.log(f"系统运行出错: {e}")
|
||||||
|
system.logger.save_logs()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
232
simple_build.py
Normal file
232
simple_build.py
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
#!/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()
|
Loading…
Reference in New Issue
Block a user