diff --git a/人员信息.xlsx b/1.xlsx similarity index 100% rename from 人员信息.xlsx rename to 1.xlsx diff --git a/座位信息.xlsx b/2.xlsx similarity index 100% rename from 座位信息.xlsx rename to 2.xlsx diff --git a/README.md b/README.md index f5a2588..4a9e248 100644 --- a/README.md +++ b/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. **智能覆盖**: 人员信息覆盖座位表对应列,分配备注单独存储 ## 🔍 故障排除 diff --git a/Windows部署说明.md b/Windows部署说明.md deleted file mode 100644 index 3c8f37f..0000000 --- a/Windows部署说明.md +++ /dev/null @@ -1,284 +0,0 @@ -# 座位分配系统 - Windows部署说明 - -## 目录 -- [概述](#概述) -- [系统要求](#系统要求) -- [开发者构建指南](#开发者构建指南) -- [用户使用指南](#用户使用指南) -- [故障排除](#故障排除) -- [技术支持](#技术支持) - -## 概述 - -本文档说明如何在Windows系统上构建和部署座位分配系统。该系统支持1-10人连坐需求,能够处理不连续座位分配。 - -**主要功能:** -- 智能座位分配算法 -- 支持连坐组合(1-10人) -- 完整的文件校验 -- 详细的分配日志 -- 用户友好的命令行界面 - -## 系统要求 - -### 开发环境要求 -- **操作系统**: Windows 7/10/11 (64位) -- **Python版本**: Python 3.8 或更高版本 -- **内存**: 至少 2GB RAM -- **磁盘空间**: 至少 500MB 可用空间 -- **网络**: 需要网络连接以下载依赖包 - -### 运行环境要求(最终用户) -- **操作系统**: Windows 7/10/11 (64位) -- **内存**: 至少 1GB RAM -- **磁盘空间**: 至少 100MB 可用空间 -- **Excel**: Microsoft Excel 2010 或更高版本(查看结果文件) - -## 开发者构建指南 - -### 1. 环境准备 - -#### 1.1 安装Python -从 [Python官网](https://www.python.org/downloads/) 下载并安装Python 3.8+ - -确保安装时勾选: -- ✅ Add Python to PATH -- ✅ Install pip - -#### 1.2 验证安装 -```cmd -python --version -pip --version -``` - -### 2. 下载源代码 - -```cmd -git clone -cd TableSynthesis -``` - -### 3. 自动构建(推荐) - -#### 3.1 运行构建脚本 -```cmd -python windows_build.py -``` - -构建脚本将自动完成: -- ✅ 检查系统环境 -- ✅ 安装必要依赖 -- ✅ 构建独立exe文件 -- ✅ 创建分发包 - -#### 3.2 构建输出 -构建成功后,会生成: -``` -座位分配系统_Windows_分发包/ -├── 座位分配系统.exe # 主程序 -├── 运行座位分配系统.bat # 启动脚本 -├── 使用说明.txt # 使用说明 -├── 人员信息_示例.xlsx # 示例文件 -└── 座位信息_示例.xlsx # 示例文件 -``` - -### 4. 手动构建(可选) - -如果自动构建失败,可以手动构建: - -#### 4.1 安装依赖 -```cmd -pip install pandas>=1.3.0 openpyxl>=3.0.0 numpy>=1.20.0 pyinstaller>=4.0 -``` - -#### 4.2 使用PyInstaller构建 -```cmd -pyinstaller --clean --noconfirm seat_allocation.spec -``` - -#### 4.3 手动创建分发包 -```cmd -mkdir 座位分配系统_Windows_分发包 -copy dist\座位分配系统.exe 座位分配系统_Windows_分发包\ -copy 人员信息.xlsx 座位分配系统_Windows_分发包\人员信息_示例.xlsx -copy 座位信息.xlsx 座位分配系统_Windows_分发包\座位信息_示例.xlsx -copy 运行座位分配系统.bat 座位分配系统_Windows_分发包\ -``` - -## 用户使用指南 - -### 1. 获取分发包 - -从开发者处获取 `座位分配系统_Windows_分发包` 文件夹,或下载已构建的发布版本。 - -### 2. 准备数据文件 - -#### 2.1 复制示例文件 -``` -人员信息_示例.xlsx → 人员信息.xlsx -座位信息_示例.xlsx → 座位信息.xlsx -``` - -#### 2.2 填写人员信息.xlsx - -**必需列:** -- **姓名**: 人员姓名 -- **证件类型**: 身份证、护照等 -- **证件号**: 证件号码 -- **手机号**: 联系电话 -- **备注**: 连坐人数(留空表示单独坐) - -**连坐规则:** -- 单人坐位:备注列留空 -- 连坐组合:第一人填写总人数,其他人留空 - -**示例:** -| 姓名 | 证件类型 | 证件号 | 手机号 | 备注 | -|------|----------|--------|--------|------| -| 张三 | 身份证 | 123456789012345678 | 13800138000 | 3 | -| 李四 | 身份证 | 123456789012345679 | 13800138001 | | -| 王五 | 身份证 | 123456789012345680 | 13800138002 | | -| 赵六 | 身份证 | 123456789012345681 | 13800138003 | | - -上述示例表示:张三、李四、王五需要3人连坐,赵六单独坐。 - -#### 2.3 填写座位信息.xlsx - -**必需列:** -- **区域**: 座位区域(如"A区"、"B区") -- **楼层**: 楼层信息(如"1层"、"2层") -- **排号**: 排号(如"第1排"、"第2排") -- **座位号**: 具体座位号(如"1号"、"2号") - -**示例:** -| 区域 | 楼层 | 排号 | 座位号 | -|------|------|------|--------| -| A区 | 1层 | 第1排 | 1号 | -| A区 | 1层 | 第1排 | 2号 | -| A区 | 1层 | 第1排 | 3号 | - -### 3. 运行程序 - -#### 3.1 推荐方式(使用bat脚本) -双击 `运行座位分配系统.bat` - -脚本会自动: -- ✅ 检查环境和文件 -- ✅ 提供友好的错误提示 -- ✅ 运行主程序 -- ✅ 显示运行结果 - -#### 3.2 直接运行 -双击 `座位分配系统.exe` - -### 4. 查看结果 - -程序运行成功后,会生成以下文件: - -- **座位信息_最终分配.xlsx**: 最终座位分配结果 -- **最终座位分配日志.xlsx**: 详细分配记录 -- **seat_allocation_log.txt**: 运行日志 - -使用Excel打开xlsx文件查看分配结果。 - -## 故障排除 - -### 常见问题 - -#### Q1: 程序无法启动 -**可能原因:** -- 系统不兼容(需要Windows 7+) -- 缺少Visual C++ Redistributable - -**解决方案:** -1. 确认系统版本:Windows 7/10/11 64位 -2. 下载安装 [Microsoft Visual C++ Redistributable](https://aka.ms/vs/17/release/vc_redist.x64.exe) - -#### Q2: 提示缺少依赖包 -**可能原因:** -- 网络连接问题 -- 权限不足 - -**解决方案:** -1. 确保网络连接正常 -2. 以管理员身份运行 -3. 手动安装:`pip install pandas openpyxl numpy` - -#### Q3: 文件权限错误 -**可能原因:** -- Excel文件被占用 -- 程序目录只读 - -**解决方案:** -1. 关闭所有Excel窗口 -2. 确保程序目录有写入权限 -3. 避免在系统盘根目录运行 - -#### Q4: 数据文件格式错误 -**可能原因:** -- 列名不匹配 -- 数据类型错误 -- 编码问题 - -**解决方案:** -1. 对照示例文件检查列名 -2. 确保数据格式正确 -3. 使用UTF-8编码保存 - -#### Q5: 无法找到连续座位 -**可能原因:** -- 座位信息不连续 -- 连坐组过大 - -**解决方案:** -1. 检查座位信息的连续性 -2. 适当调整连坐组大小 -3. 增加座位数量 - -### 日志分析 - -查看 `seat_allocation_log.txt` 文件可获取详细错误信息: - -- **文件加载错误**: 检查文件路径和格式 -- **数据校验错误**: 检查数据完整性 -- **分配算法错误**: 检查座位和人员匹配 - -### 性能优化 - -#### 大数据处理 -- **人员数量**: 建议不超过1000人 -- **座位数量**: 建议不超过2000个 -- **连坐组大小**: 建议不超过8人 - -#### 内存使用 -- 小型场景(<100人): 约50MB内存 -- 中型场景(100-500人): 约100MB内存 -- 大型场景(500-1000人): 约200MB内存 - -## 技术支持 - -### 获取帮助 - -1. **查看日志文件**: `seat_allocation_log.txt` -2. **检查使用说明**: `使用说明.txt` -3. **参考示例文件**: 确保数据格式正确 - -### 报告问题 - -报告问题时,请提供: -- 系统信息(操作系统版本) -- 错误截图或错误信息 -- 数据文件(脱敏后) -- seat_allocation_log.txt 内容 - -### 更新与维护 - -- 定期检查更新版本 -- 备份重要数据文件 -- 保持系统和依赖包更新 - ---- - -**版本**: v1.0 -**最后更新**: 2024年12月 -**兼容性**: Windows 7/10/11 (64位) \ No newline at end of file diff --git a/build_workflow.yml b/build_workflow.yml deleted file mode 100644 index b2cfde3..0000000 --- a/build_workflow.yml +++ /dev/null @@ -1,89 +0,0 @@ -# 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 }} diff --git a/cross_platform_build.py b/cross_platform_build.py deleted file mode 100644 index 5a660a5..0000000 --- a/cross_platform_build.py +++ /dev/null @@ -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() diff --git a/log/seat_allocation_log.txt b/log/seat_allocation_log.txt deleted file mode 100644 index 7cf755d..0000000 --- a/log/seat_allocation_log.txt +++ /dev/null @@ -1,127 +0,0 @@ -[2025-07-01 15:41:09] ============================================================ -[2025-07-01 15:41:09] 座位分配系统 - 文件校验 -[2025-07-01 15:41:09] ============================================================ -[2025-07-01 15:41:09] === 开始加载数据 === -[2025-07-01 15:41:09] 正在读取人员信息文件: 人员信息.xlsx -[2025-07-01 15:41:09] 正在读取座位信息文件: 座位信息.xlsx -[2025-07-01 15:41:09] 开始过滤座位数据... -[2025-07-01 15:41:09] 原始数据行数: 17 -[2025-07-01 15:41:09] 过滤后数据行数: 6 -[2025-07-01 15:41:09] 移除无效行数: 11 -[2025-07-01 15:41:09] ✅ 已过滤掉 11 行无效数据(空行、示例数据等) -[2025-07-01 15:41:09] 有效座位区域: 西北门入口场地A5区 -[2025-07-01 15:41:09] 有效座位楼层: 场地 -[2025-07-01 15:41:09] 清理文字数据中的空格... -[2025-07-01 15:41:09] ✅ 文字数据清理完成 -[2025-07-01 15:41:09] ✅ 文件加载成功 -[2025-07-01 15:41:09] 人员信息: 6 行 × 6 列 -[2025-07-01 15:41:09] 座位信息: 6 行 × 12 列 -[2025-07-01 15:41:09] -=== 人员信息结构校验 === -[2025-07-01 15:41:09] ✅ 所有必需列都存在 -[2025-07-01 15:41:09] -数据完整性检查: -[2025-07-01 15:41:09] ✅ 姓名 列数据完整 -[2025-07-01 15:41:09] ✅ 证件类型 列数据完整 -[2025-07-01 15:41:09] ✅ 证件号 列数据完整 -[2025-07-01 15:41:09] ✅ 手机号 列数据完整 -[2025-07-01 15:41:09] ✅ 姓名无重复 -[2025-07-01 15:41:09] -=== 连坐组校验 === -[2025-07-01 15:41:09] ✅ 第 1 组: 叶一帆 (单独) -[2025-07-01 15:41:09] ✅ 第 2 组: 刘泽 (单独) -[2025-07-01 15:41:09] ✅ 第 3 组: 黄锡恩 (单独) -[2025-07-01 15:41:09] ✅ 第 4 组: 胡中, 丁逸夫 (2人连坐) -[2025-07-01 15:41:09] ✅ 第 5 组: 沈佳琰 (单独) -[2025-07-01 15:41:09] -=== 座位信息结构校验 === -[2025-07-01 15:41:09] ✅ 所有必需列都存在 -[2025-07-01 15:41:09] ✅ 区域 列数据完整 -[2025-07-01 15:41:09] ✅ 楼层 列数据完整 -[2025-07-01 15:41:09] ✅ 排号 列数据完整 -[2025-07-01 15:41:09] ✅ 座位号 列数据完整 -[2025-07-01 15:41:09] -座位结构分析: -[2025-07-01 15:41:09] -座位结构分析: -[2025-07-01 15:41:09] ✅ 西北门入口场地A5区-场地-19排: 6 个座位完全连续 (1-6) -[2025-07-01 15:41:09] -=== 容量和可行性校验 === -[2025-07-01 15:41:09] 总人数: 6 -[2025-07-01 15:41:09] 总座位数: 6 -[2025-07-01 15:41:09] ✅ 座位充足: 剩余 0 个座位 -[2025-07-01 15:41:09] -连坐组需求分析: -[2025-07-01 15:41:09] 最大连坐组: 2 人 -[2025-07-01 15:41:09] -连续座位可行性分析: -[2025-07-01 15:41:09] 西北门入口场地A5区-场地-19排: 最大连续 6 个座位 -[2025-07-01 15:41:09] -全场最大连续座位: 6 个 -[2025-07-01 15:41:09] ✅ 可以容纳最大连坐组 -[2025-07-01 15:41:09] 可容纳最大连坐组的排数: 1 个 -[2025-07-01 15:41:09] -连坐组分布: -[2025-07-01 15:41:09] 单人组: 4 个 -[2025-07-01 15:41:09] 2人连坐组: 1 个 -[2025-07-01 15:41:09] -============================================================ -[2025-07-01 15:41:09] 校验结果总结 -[2025-07-01 15:41:09] ============================================================ -[2025-07-01 15:41:09] 人员信息结构: ✅ 通过 -[2025-07-01 15:41:09] 连坐组完整性: ✅ 通过 -[2025-07-01 15:41:09] 座位信息结构: ✅ 通过 -[2025-07-01 15:41:09] 容量可行性: ✅ 通过 -[2025-07-01 15:41:09] -总体校验结果: ✅ 全部通过 -[2025-07-01 15:41:09] -🎉 文件校验通过,可以进行座位分配! -[2025-07-01 15:41:19] -============================================================ -[2025-07-01 15:41:19] 开始座位分配 -[2025-07-01 15:41:19] ============================================================ -[2025-07-01 15:41:19] -=== 人员连坐需求分析 === -[2025-07-01 15:41:19] 总共识别出 5 个座位组: -[2025-07-01 15:41:19] 单人组: 4 个 -[2025-07-01 15:41:19] 2人连坐组: 1 个 -[2025-07-01 15:41:19] -=== 高级座位结构分析 === -[2025-07-01 15:41:19] ✅ 西北门入口场地A5区-场地-19排: 6 个座位完全连续 -[2025-07-01 15:41:19] -=== 开始智能座位分配 === -[2025-07-01 15:41:19] 需要分配 5 个组 -[2025-07-01 15:41:19] -处理第 1 组: 胡中 (group, 2 人) -[2025-07-01 15:41:19] 分配到 西北门入口场地A5区-场地-19排 (连续座位: 1-2) -[2025-07-01 15:41:19] 胡中 -> 1号 -[2025-07-01 15:41:19] 丁逸夫 -> 2号 -[2025-07-01 15:41:19] -处理第 2 组: 叶一帆 (single, 1 人) -[2025-07-01 15:41:19] 分配到 西北门入口场地A5区-场地-19排: 叶一帆 -> 3号 -[2025-07-01 15:41:19] -处理第 3 组: 刘泽 (single, 1 人) -[2025-07-01 15:41:19] 分配到 西北门入口场地A5区-场地-19排: 刘泽 -> 4号 -[2025-07-01 15:41:19] -处理第 4 组: 黄锡恩 (single, 1 人) -[2025-07-01 15:41:19] 分配到 西北门入口场地A5区-场地-19排: 黄锡恩 -> 5号 -[2025-07-01 15:41:19] -处理第 5 组: 沈佳琰 (single, 1 人) -[2025-07-01 15:41:19] 分配到 西北门入口场地A5区-场地-19排: 沈佳琰 -> 6号 -[2025-07-01 15:41:19] -座位分配结果已保存到: log/座位信息_最终分配.xlsx -[2025-07-01 15:41:19] 分配日志已保存到: log/最终座位分配日志.xlsx -[2025-07-01 15:41:19] -=== 分配统计 === -[2025-07-01 15:41:19] 总共分配了 6 个座位 -[2025-07-01 15:41:19] -按组大小统计: -[2025-07-01 15:41:19] 单人组: 4 个组, 4 人 -[2025-07-01 15:41:19] 2人连坐组: 1 个组, 2 人 -[2025-07-01 15:41:19] -=== 连续性验证 === -[2025-07-01 15:41:19] ✅ 组 1 (胡中): 2人连坐,座位连续 [1, 2] -[2025-07-01 15:41:19] -连续性检查结果: 1/1 个多人组座位连续 (100.0%) -[2025-07-01 15:41:19] -🎉 座位分配完成! \ No newline at end of file diff --git a/log/座位信息_最终分配.xlsx b/log/座位信息_最终分配.xlsx deleted file mode 100644 index d505a12..0000000 Binary files a/log/座位信息_最终分配.xlsx and /dev/null differ diff --git a/log/最终座位分配日志.xlsx b/log/最终座位分配日志.xlsx deleted file mode 100644 index b121c8d..0000000 Binary files a/log/最终座位分配日志.xlsx and /dev/null differ diff --git a/seat_allocation.spec b/seat_allocation.spec deleted file mode 100644 index 7c38a5d..0000000 --- a/seat_allocation.spec +++ /dev/null @@ -1,128 +0,0 @@ -# -*- 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', - 'openpyxl.formatting.rule', - - # pandas相关 - 'pandas.io.excel', - 'pandas.io.common', - 'pandas.io.parsers', - 'pandas.io.formats', - 'pandas.io.formats.excel', - 'pandas._libs.tslibs.timedeltas', - 'pandas._libs.tslibs.np_datetime', - 'pandas._libs.tslibs.nattype', - 'pandas._libs.skiplist', - - # numpy相关 - 'numpy.core.multiarray', - 'numpy.core.umath', - 'numpy.core._methods', - 'numpy.lib.format', - 'numpy.random._pickle', - - # 编码相关 - 'encodings', - 'encodings.utf_8', - 'encodings.gbk', - 'encodings.cp1252', - - # 其他必要模块 - 'pkg_resources.py2_warn', - '_ctypes', - 'ctypes.util' - ], - hookspath=[], - hooksconfig={}, - runtime_hooks=[], - excludes=[ - # 排除不必要的大型库 - 'matplotlib', - 'scipy', - 'IPython', - 'jupyter', - 'notebook', - 'tkinter', - 'PyQt5', - 'PyQt6', - 'PySide2', - 'PySide6', - 'test', - 'tests', - 'unittest', - 'doctest', - 'pdb', - 'pydoc', - 'setuptools', - 'pip', - 'wheel', - 'distutils' - ], - 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压缩以提高兼容性 - upx_exclude=[], - runtime_tmpdir=None, - console=True, - disable_windowed_traceback=False, - argv_emulation=False, - target_arch=None, - codesign_identity=None, - entitlements_file=None, - # 确保包含Python运行时 - exclude_binaries=False, -) \ No newline at end of file diff --git a/seat_allocation_log.txt b/seat_allocation_log.txt deleted file mode 100644 index beff68a..0000000 --- a/seat_allocation_log.txt +++ /dev/null @@ -1,85 +0,0 @@ -[2025-07-01 15:33:27] ============================================================ -[2025-07-01 15:33:27] 座位分配系统 - 文件校验 -[2025-07-01 15:33:27] ============================================================ -[2025-07-01 15:33:27] === 开始加载数据 === -[2025-07-01 15:33:27] 正在读取人员信息文件: 人员信息.xlsx -[2025-07-01 15:33:27] 正在读取座位信息文件: 座位信息.xlsx -[2025-07-01 15:33:27] 清理文字数据中的空格... -[2025-07-01 15:33:27] ✅ 文字数据清理完成 -[2025-07-01 15:33:27] ✅ 文件加载成功 -[2025-07-01 15:33:27] 人员信息: 6 行 × 6 列 -[2025-07-01 15:33:27] 座位信息: 17 行 × 12 列 -[2025-07-01 15:33:27] -=== 人员信息结构校验 === -[2025-07-01 15:33:27] ✅ 所有必需列都存在 -[2025-07-01 15:33:27] -数据完整性检查: -[2025-07-01 15:33:27] ✅ 姓名 列数据完整 -[2025-07-01 15:33:27] ✅ 证件类型 列数据完整 -[2025-07-01 15:33:27] ✅ 证件号 列数据完整 -[2025-07-01 15:33:27] ✅ 手机号 列数据完整 -[2025-07-01 15:33:27] ✅ 姓名无重复 -[2025-07-01 15:33:27] -=== 连坐组校验 === -[2025-07-01 15:33:27] ✅ 第 1 组: 叶一帆 (单独) -[2025-07-01 15:33:27] ✅ 第 2 组: 刘泽 (单独) -[2025-07-01 15:33:27] ✅ 第 3 组: 黄锡恩 (单独) -[2025-07-01 15:33:27] ✅ 第 4 组: 胡中, 丁逸夫 (2人连坐) -[2025-07-01 15:33:27] ✅ 第 5 组: 沈佳琰 (单独) -[2025-07-01 15:33:27] -=== 座位信息结构校验 === -[2025-07-01 15:33:27] ✅ 所有必需列都存在 -[2025-07-01 15:33:27] ❌ 区域 列有 11 个空值 -[2025-07-01 15:33:27] ❌ 楼层 列有 11 个空值 -[2025-07-01 15:33:27] ❌ 排号 列有 11 个空值 -[2025-07-01 15:33:27] ❌ 座位号 列有 11 个空值 -[2025-07-01 15:33:27] -座位结构分析: -[2025-07-01 15:33:27] -座位结构分析: -[2025-07-01 15:33:27] ✅ 西北门入口场地A5区-场地-19排: 6 个座位完全连续 (1-6) -[2025-07-01 15:33:27] ❌ nan-nan-nan: 座位号格式异常 'nan' -[2025-07-01 15:33:27] ❌ nan-nan-nan: 座位号格式异常 'nan' -[2025-07-01 15:33:27] ❌ nan-nan-nan: 座位号格式异常 'nan' -[2025-07-01 15:33:27] ❌ nan-nan-nan: 座位号格式异常 'nan' -[2025-07-01 15:33:27] ❌ nan-nan-nan: 座位号格式异常 'nan' -[2025-07-01 15:33:27] ❌ nan-nan-nan: 座位号格式异常 'nan' -[2025-07-01 15:33:27] ❌ nan-nan-nan: 座位号格式异常 'nan' -[2025-07-01 15:33:27] ❌ nan-nan-nan: 座位号格式异常 'nan' -[2025-07-01 15:33:27] ❌ nan-nan-nan: 座位号格式异常 'nan' -[2025-07-01 15:33:27] ❌ nan-nan-nan: 座位号格式异常 'nan' -[2025-07-01 15:33:27] ❌ nan-nan-nan: 座位号格式异常 'nan' -[2025-07-01 15:33:27] 📊 nan-nan-nan: 11 个座位,0 个连续段: -[2025-07-01 15:33:27] 最大连续段: 0 个座位 -[2025-07-01 15:33:27] -=== 容量和可行性校验 === -[2025-07-01 15:33:27] 总人数: 6 -[2025-07-01 15:33:27] 总座位数: 17 -[2025-07-01 15:33:27] ✅ 座位充足: 剩余 11 个座位 -[2025-07-01 15:33:27] -连坐组需求分析: -[2025-07-01 15:33:27] 最大连坐组: 2 人 -[2025-07-01 15:33:27] -连续座位可行性分析: -[2025-07-01 15:33:27] 西北门入口场地A5区-场地-19排: 最大连续 6 个座位 -[2025-07-01 15:33:27] nan-nan-nan: 最大连续 0 个座位 -[2025-07-01 15:33:27] -全场最大连续座位: 6 个 -[2025-07-01 15:33:27] ✅ 可以容纳最大连坐组 -[2025-07-01 15:33:27] 可容纳最大连坐组的排数: 1 个 -[2025-07-01 15:33:27] -连坐组分布: -[2025-07-01 15:33:27] 单人组: 4 个 -[2025-07-01 15:33:27] 2人连坐组: 1 个 -[2025-07-01 15:33:27] -============================================================ -[2025-07-01 15:33:27] 校验结果总结 -[2025-07-01 15:33:27] ============================================================ -[2025-07-01 15:33:27] 人员信息结构: ✅ 通过 -[2025-07-01 15:33:27] 连坐组完整性: ✅ 通过 -[2025-07-01 15:33:27] 座位信息结构: ❌ 失败 -[2025-07-01 15:33:27] 容量可行性: ✅ 通过 -[2025-07-01 15:33:27] -总体校验结果: ❌ 存在问题 -[2025-07-01 15:33:27] -⚠️ 请修复上述问题后再进行座位分配。 \ No newline at end of file diff --git a/seat_allocation_system.py b/seat_allocation_system.py index 9d8ff11..deaaeca 100644 --- a/seat_allocation_system.py +++ b/seat_allocation_system.py @@ -10,6 +10,8 @@ import sys import os from pathlib import Path import datetime +import pandas as pd +import numpy as np def check_dependencies(): """检查并安装必要的依赖包""" @@ -192,16 +194,8 @@ def check_data_files(): return True, str(personnel_file), str(seat_file) -# 只有在依赖检查通过后才导入这些包 -if check_dependencies(): - try: - import pandas as pd - import numpy as np - except ImportError as e: - print(f"❌ 导入依赖包失败: {e}") - input("按Enter键退出...") - sys.exit(1) -else: +# 检查依赖 +if not check_dependencies(): print("❌ 依赖检查失败") input("按Enter键退出...") sys.exit(1) @@ -253,10 +247,211 @@ class SeatAllocationSystem: 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): """加载人员信息和座位信息数据""" @@ -382,23 +577,23 @@ class SeatAllocationSystem: 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 ['姓名', '证件类型', '证件号', '手机号']: @@ -410,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): @@ -693,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 @@ -710,7 +988,8 @@ class SeatAllocationSystem: 'type': 'single', 'size': 1, 'members': [person], - 'leader': person['姓名'] + 'leader': person['姓名'], + 'grouping_method': 'remark' }) i += 1 else: @@ -727,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 @@ -738,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: @@ -861,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, @@ -945,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, @@ -990,6 +1272,15 @@ class SeatAllocationSystem: 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: @@ -1067,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 @@ -1120,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 # 高级座位结构分析 @@ -1139,6 +1437,29 @@ 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(): """主函数""" print("=" * 60) diff --git a/simple_build.py b/simple_build.py deleted file mode 100644 index 91497e1..0000000 --- a/simple_build.py +++ /dev/null @@ -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() diff --git a/运行座位分配系统.bat b/运行座位分配系统.bat deleted file mode 100644 index aafa296..0000000 --- a/运行座位分配系统.bat +++ /dev/null @@ -1,116 +0,0 @@ -@echo off -chcp 65001 >nul -title 座位分配系统 v2.0 - -:: 设置颜色 -color 0F - -echo. -echo ========================================== -echo 座位分配系统 v2.0 -echo ========================================== -echo. -echo 正在检查运行环境... -echo. - -:: 检查可执行文件是否存在 -if not exist "座位分配系统.exe" ( - echo [错误] 未找到 座位分配系统.exe 文件 - echo. - echo 请确保以下文件在同一目录下: - echo - 座位分配系统.exe - echo - 人员信息.xlsx - echo - 座位信息.xlsx - echo. - pause - exit /b 1 -) - -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. - echo 当前Excel文件: - for %%f in (*.xlsx) do echo %%f - echo. - echo 请移除多余文件后重试 - pause - exit /b 1 -) - -echo [成功] 找到 %count% 个Excel文件,程序将自动识别文件类型 - -echo. -echo ========================================== -echo 开始运行程序 -echo ========================================== -echo. -echo 所有检查通过,正在启动座位分配系统... -echo 请等待程序运行完成... -echo. - -:: 运行程序并捕获错误 -"座位分配系统.exe" -set "exit_code=%ERRORLEVEL%" - -echo. -echo ========================================== -echo 运行结果 -echo ========================================== -echo. - -if %exit_code% equ 0 ( - echo [成功] 程序运行成功! - echo. - echo 输出文件说明: - echo - 座位信息_最终分配.xlsx: 最终座位分配结果 - echo - 最终座位分配日志.xlsx: 详细分配记录 - echo - seat_allocation_log.txt: 运行日志文件 - echo. - echo 您可以用Excel打开xlsx文件查看结果 -) else ( - echo [错误] 程序运行出现错误 (错误代码: %exit_code%) - echo. - echo 可能的原因: - echo 1. 数据文件格式不正确 - echo 2. 文件权限不足 - echo 3. 磁盘空间不足 - echo 4. 缺少必要的依赖 - echo. - echo 请查看 seat_allocation_log.txt 获取详细错误信息 -) - -echo. -echo 按任意键退出... -pause >nul \ No newline at end of file