Ver código fonte

refactor: 更新代码以使用格式化字符串替代f-string,增强日志记录功能

- 在app.py中,将f-string替换为str.format()以提高兼容性。
- 在config.py中添加日志记录功能,记录加载和保存配置的详细信息。
- 更新build.py以确保在构建过程中正确处理文件路径和依赖项。
- 修改README.md以反映项目的最新功能和安装说明。
- 更新requirements.txt以调整依赖包版本,确保兼容性。
master
lizhuang 1 semana atrás
pai
commit
17e75cb6e7

+ 3
- 3
ExcelConverter.spec Ver arquivo

@@ -2,10 +2,10 @@


a = Analysis(
['app.py'],
pathex=[],
['main.py'],
pathex=['D:\\2025Project\\MCP\\excel_converter'],
binaries=[],
datas=[('employee_info.json', '.'), ('company_options.json', '.'), ('bank_options.json', '.'), ('template.xlsx', '.'), ('config.py', '.')],
datas=[('D:\\2025Project\\MCP\\excel_converter\\config.py', '.'), ('D:\\2025Project\\MCP\\excel_converter\\template.xlsx', '.')],
hiddenimports=[],
hookspath=[],
hooksconfig={},

+ 48
- 87
README.md Ver arquivo

@@ -1,115 +1,76 @@
# 工资明细转换工具
# 日本工资明细转换工具

这是一个用于批量转换Excel工资明细表的图形界面工具。
## 功能简介

## 功能特点
此工具用于将日本工资明细表格数据转换为标准格式,支持批量处理多名员工的数据。

1. 批量导入Excel文件并根据模板进行转换
2. 自定义设置每个文件的公司、银行和其他信息
3. 按照指定的单元格映射规则转换数据
4. 自动生成包含员工姓名和日期的输出文件名
5. 支持复制和保留原始Excel的格式和样式
## 主要功能

## 环境要求
1. 导入Excel格式的工资明细表
2. 维护员工信息(姓名、公司、银行账户等)
3. 自动转换并生成标准格式的工资明细表
4. 批量处理多个员工的数据

- Python 3.7 或更高版本
- 依赖包:pandas, openpyxl, xlrd, xlwt
## 安装说明

## 快速开始
### 开发环境安装

1. 确保已安装Python环境(3.7或更高版本)
2. 使用uv创建虚拟环境:
```bash
uv venv
1. 确保已安装Python 3.6+
2. 安装依赖包:
```
3. 激活虚拟环境:
```bash
# Windows
.venv\Scripts\activate
# macOS/Linux
source .venv/bin/activate
pip install -r requirements.txt
```
4. 安装依赖包:
```bash
uv pip install -r requirements.txt
3. 运行程序:
```
5. 运行程序:
```bash
# 方法1: 直接运行Python脚本
python main.py
# 方法2: 使用批处理文件 (Windows)
run.bat
```

## 使用方法
### 构建可执行文件

1. 在界面上点击"选择Excel文件"按钮选择要转换的Excel文件
2. 双击列表中的每个文件,设置公司信息(C2)、银行信息(B30)和其他信息(F2)
3. 点击"选择导出位置"按钮选择输出文件保存位置
4. 点击"开始转换"按钮进行批量转换
5. 转换完成后会显示成功转换的文件数量
要构建独立的可执行文件,请运行:

## 文件命名规则

输出文件名格式为:`YYYY年M月份給料明細書-姓名.xls`
- 年份(YYYY):从输入文件第二个Sheet页的B4单元格中提取
- 月份(M):从输入文件第二个Sheet页的B4单元格中提取
- 姓名:从输入文件第一个Sheet页的C3单元格中提取
```
python build.py
```

## 配置指南
构建完成后,可执行文件将位于`dist`目录中。

### 修改模板路径
在 `config.py` 文件中修改 `TEMPLATE_PATH` 变量。
## 常见问题解决

### 修改单元格映射关系
在 `config.py` 文件中的 `CELL_MAPPINGS` 字典中定义映射关系:
```python
{
(源文件sheet索引, 行, 列): (目标文件sheet索引, 行, 列)
}
```
### 关于"信息维护无法保存"的问题

### 修改公司和银行列表
编辑 `config.py` 文件中的 `COMPANY_OPTIONS` 和 `BANK_OPTIONS` 列表即可添加或删除选项。
如果使用打包后的程序遇到信息维护功能无法保存的问题,请按照以下步骤解决:

## 常见问题解决
1. 确保运行程序时有足够的权限(管理员权限)
2. 检查程序所在目录是否有写入权限
3. 手动创建一个名为`employee_info.json`的空文件:
- 在程序所在的文件夹中创建一个文本文件
- 内容填写`[]`(一个空数组)
- 将文件重命名为`employee_info.json`
4. 重新启动程序,应该就可以正常保存员工信息了

### 程序启动失败
- 检查 Python 版本是否兼容 (3.7+)
- 确认所有依赖已正确安装
- 检查模板文件是否存在于指定路径
### 数据文件路径说明

### 转换后的文件格式不正确
- 确认模板文件格式无误
- 检查输入文件是否符合要求的格式
- 确认映射关系配置正确
打包后的程序会在以下位置存储数据文件:
- 员工信息: 程序同一目录下的`employee_info.json`
- 模板文件: 程序同一目录下的`template.xlsx`

### 无法识别日期
如果程序无法正确识别日期格式,可能需要在 `app.py` 的 `process_file` 方法中添加更多的日期解析逻辑。
## 使用说明

## 项目结构
1. 启动程序
2. 点击"选择Excel文件"导入原始工资明细表
3. 点击"信息维护"添加或更新员工信息
4. 选择导出位置
5. 点击"开始转换"生成标准格式的工资明细表

```
excel_converter/
├── app.py # 主应用程序代码
├── config.py # 配置文件
├── main.py # 入口点
├── requirements.txt # 依赖列表
├── run.bat # Windows 运行脚本
├── README.md # 项目说明
├── template.xlsx # Excel模板文件
└── test_data/ # 测试数据目录
```
## 开发者说明

## 维护和扩展
本程序基于Python开发,使用以下库:
- tkinter: 用于GUI界面
- openpyxl: 用于Excel文件处理
- pandas: 用于数据处理
- xlwings: 用于高级Excel操作

### 添加新功能
1. 尽量将配置参数放在 `config.py` 文件中
2. 保持用户界面简洁直观
3. 添加适当的错误处理和用户反馈
## 联系方式

### 代码维护
- 定期更新依赖包版本
- 如果更改了核心功能,请更新文档
- 考虑添加单元测试以确保功能正常
如有问题,请联系开发者。

BIN
__pycache__/app.cpython-312.pyc Ver arquivo


BIN
__pycache__/config.cpython-312.pyc Ver arquivo


+ 39
- 1084
app.py
Diferenças do arquivo suprimidas por serem muito extensas
Ver arquivo


+ 69
- 21
build.py Ver arquivo

@@ -1,32 +1,80 @@
# -*- coding: utf-8 -*-
"""
Build script for Excel Converter application
This script uses PyInstaller to build an executable from the Python code
"""

import os
import subprocess
import sys
import subprocess
import shutil

def build_executable():
# Ensure we're in the project root directory
os.chdir(os.path.dirname(os.path.abspath(__file__)))
def main():
# Package name and path
package_name = "ExcelConverter"
main_script = "main.py"
icon_path = "icon.ico"
# Install required packages using uv
print("Installing required packages...")
subprocess.run(["uv", "pip", "install", "-r", "requirements.txt"], check=True)
# Make sure we're in the right directory
script_dir = os.path.dirname(os.path.abspath(__file__))
os.chdir(script_dir)
# Build the executable using PyInstaller
print("Building executable...")
subprocess.run([
# Create build command
cmd = [
"pyinstaller",
"--name=ExcelConverter",
"--name={}".format(package_name),
"--onefile",
"--windowed",
"--add-data=employee_info.json;.",
"--add-data=company_options.json;.",
"--add-data=bank_options.json;.",
"--add-data=template.xlsx;.",
"--add-data=config.py;.",
"app.py"
], check=True)
"--clean",
"--paths={}".format(script_dir),
"--add-data={}{}config.py;.".format(script_dir, os.path.sep),
"--add-data={}{}template.xlsx;.".format(script_dir, os.path.sep)
]
# Add icon if it exists
if os.path.exists(icon_path):
cmd.append("--icon={}".format(icon_path))
# Add main script
cmd.append(main_script)
# Print command
print("Running command: {}".format(" ".join(cmd)))
# Run PyInstaller
try:
subprocess.check_call(cmd)
print("\nBuild completed successfully!")
# Copy necessary files to dist folder
dist_dir = os.path.join(script_dir, "dist", package_name)
if not os.path.exists(dist_dir):
dist_dir = os.path.join(script_dir, "dist")
# Copy template file to dist folder
if os.path.exists("template.xlsx"):
shutil.copy("template.xlsx", dist_dir)
print("Copied template.xlsx to {}".format(dist_dir))
# Create empty config file if it doesn't exist
config_path = os.path.join(dist_dir, "employee_info.json")
if not os.path.exists(config_path):
with open(config_path, 'w', encoding='utf-8') as f:
f.write("[]")
print("Created empty employee_info.json in {}".format(dist_dir))
print("\nBuild is ready in: {}".format(dist_dir))
except subprocess.CalledProcessError as e:
print("Build failed with error:")
print(e)
return 1
except Exception as e:
print("An error occurred:")
print(e)
return 1
print("Build completed successfully!")
print("The executable can be found in the 'dist' directory.")
return 0

if __name__ == "__main__":
build_executable()
sys.exit(main())

+ 4050
- 4066
build/ExcelConverter/Analysis-00.toc
Diferenças do arquivo suprimidas por serem muito extensas
Ver arquivo


+ 3154
- 3163
build/ExcelConverter/EXE-00.toc
Diferenças do arquivo suprimidas por serem muito extensas
Ver arquivo


BIN
build/ExcelConverter/ExcelConverter.pkg Ver arquivo


+ 3153
- 3162
build/ExcelConverter/PKG-00.toc
Diferenças do arquivo suprimidas por serem muito extensas
Ver arquivo


BIN
build/ExcelConverter/PYZ-00.pyz Ver arquivo


+ 1
- 0
build/ExcelConverter/PYZ-00.toc Ver arquivo

@@ -35,6 +35,7 @@
('_threading_local',
'C:\\Users\\WIN\\AppData\\Roaming\\uv\\python\\cpython-3.12.3-windows-x86_64-none\\Lib\\_threading_local.py',
'PYMODULE'),
('app', 'D:\\2025Project\\MCP\\excel_converter\\app.py', 'PYMODULE'),
('argparse',
'C:\\Users\\WIN\\AppData\\Roaming\\uv\\python\\cpython-3.12.3-windows-x86_64-none\\Lib\\argparse.py',
'PYMODULE'),

BIN
build/ExcelConverter/base_library.zip Ver arquivo


+ 12
- 12
build/ExcelConverter/warn-ExcelConverter.txt Ver arquivo

@@ -14,8 +14,8 @@ Types if import:
IMPORTANT: Do NOT post this list to the issue-tracker. Use it as a basis for
tracking down the missing module yourself. Thanks!

missing module named grp - imported by shutil (delayed, optional), tarfile (optional), pathlib (delayed, optional), subprocess (delayed, conditional, optional), setuptools._distutils.archive_util (optional), setuptools._vendor.backports.tarfile (optional)
missing module named pwd - imported by posixpath (delayed, conditional, optional), shutil (delayed, optional), tarfile (optional), pathlib (delayed, optional), subprocess (delayed, conditional, optional), http.server (delayed, optional), netrc (delayed, conditional), getpass (delayed), setuptools._distutils.util (delayed, conditional, optional), setuptools._distutils.archive_util (optional), setuptools._vendor.backports.tarfile (optional)
missing module named grp - imported by shutil (delayed, optional), tarfile (optional), pathlib (delayed, optional), subprocess (delayed, conditional, optional), setuptools._distutils.archive_util (optional), setuptools._vendor.backports.tarfile (optional)
missing module named posix - imported by os (conditional, optional), posixpath (optional), shutil (conditional), importlib._bootstrap_external (conditional)
missing module named resource - imported by posix (top-level)
missing module named _frozen_importlib_external - imported by importlib._bootstrap (delayed), importlib (optional), importlib.abc (optional), zipimport (top-level)
@@ -49,13 +49,12 @@ missing module named _manylinux - imported by packaging._manylinux (delayed, opt
missing module named trove_classifiers - imported by setuptools.config._validate_pyproject.formats (optional)
missing module named pyimod02_importers - imported by D:\2025Project\MCP\excel_converter\.venv\Lib\site-packages\PyInstaller\hooks\rthooks\pyi_rth_pkgutil.py (delayed), D:\2025Project\MCP\excel_converter\.venv\Lib\site-packages\PyInstaller\hooks\rthooks\pyi_rth_pkgres.py (delayed)
missing module named collections.Mapping - imported by collections (optional), pytz.lazy (optional)
missing module named plotly - imported by xlwings.utils (optional), xlwings.pro.reports.main (optional)
missing module named 'matplotlib.figure' - imported by pandas.plotting._misc (conditional), xlwings.utils (optional), xlwings.pro.reports.main (optional)
missing module named 'PIL.Image' - imported by xlwings.pro.reports.main (optional)
missing module named PIL - imported by openpyxl.drawing.image (optional), xlwings.pro.reports.main (optional), xlwings.main (optional), xlwings._xlwindows (optional), xlwings._xlmac (optional)
missing module named jinja2 - imported by xlwings.pro.reports.main (optional), pandas.io.formats.style (top-level)
missing module named cryptography - imported by xlwings.pro.utils (optional)
missing module named mistune - imported by xlwings.pro.reports.markdown (optional)
missing module named 'defusedxml.ElementTree' - imported by openpyxl.xml.functions (conditional)
missing module named 'lxml.etree' - imported by openpyxl.xml.functions (conditional), pandas.io.xml (delayed), pandas.io.formats.xml (delayed), pandas.io.html (delayed)
missing module named openpyxl.tests - imported by openpyxl.reader.excel (optional)
missing module named defusedxml - imported by openpyxl.xml (delayed, optional)
missing module named lxml - imported by openpyxl.xml (delayed, optional), pandas.io.xml (conditional)
missing module named _dummy_thread - imported by numpy._core.arrayprint (optional)
missing module named 'numpy_distutils.cpuinfo' - imported by numpy.f2py.diagnose (delayed, conditional, optional)
missing module named 'numpy_distutils.fcompiler' - imported by numpy.f2py.diagnose (delayed, conditional, optional)
@@ -243,6 +242,12 @@ missing module named numpy._core.all - imported by numpy._core (top-level), nump
missing module named numpy._core.add - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
missing module named yaml - imported by numpy.__config__ (delayed)
missing module named numpy._distributor_init_local - imported by numpy (optional), numpy._distributor_init (optional)
missing module named plotly - imported by xlwings.utils (optional), xlwings.pro.reports.main (optional)
missing module named 'matplotlib.figure' - imported by pandas.plotting._misc (conditional), xlwings.utils (optional), xlwings.pro.reports.main (optional)
missing module named 'PIL.Image' - imported by xlwings.pro.reports.main (optional)
missing module named jinja2 - imported by xlwings.pro.reports.main (optional), pandas.io.formats.style (top-level)
missing module named cryptography - imported by xlwings.pro.utils (optional)
missing module named mistune - imported by xlwings.pro.reports.markdown (optional)
missing module named 'win32com.gen_py' - imported by win32com (conditional, optional)
missing module named 'appscript.reference' - imported by xlwings._xlmac (top-level)
missing module named osax - imported by xlwings._xlmac (top-level)
@@ -253,11 +258,6 @@ missing module named matplotlib - imported by pandas.plotting._core (conditional
missing module named pdfrw - imported by xlwings.pro.reports.pdf (optional)
missing module named 'matplotlib.pyplot' - imported by pandas.io.formats.style (optional), xlwings.utils (optional)
missing module named cStringIO - imported by xlrd.timemachine (conditional)
missing module named defusedxml - imported by openpyxl.xml (delayed, optional)
missing module named lxml - imported by openpyxl.xml (delayed, optional), pandas.io.xml (conditional)
missing module named 'defusedxml.ElementTree' - imported by openpyxl.xml.functions (conditional)
missing module named 'lxml.etree' - imported by openpyxl.xml.functions (conditional), pandas.io.xml (delayed), pandas.io.formats.xml (delayed), pandas.io.html (delayed)
missing module named openpyxl.tests - imported by openpyxl.reader.excel (optional)
missing module named six.moves.range - imported by six.moves (top-level), dateutil.rrule (top-level)
runtime module named six.moves - imported by dateutil.tz.tz (top-level), dateutil.tz._factories (top-level), dateutil.tz.win (top-level), dateutil.rrule (top-level)
missing module named dateutil.tz.tzfile - imported by dateutil.tz (top-level), dateutil.zoneinfo (top-level)

+ 358
- 326
build/ExcelConverter/xref-ExcelConverter.html
Diferenças do arquivo suprimidas por serem muito extensas
Ver arquivo


+ 67
- 13
config.py Ver arquivo

@@ -1,16 +1,37 @@
# -*- coding: utf-8 -*-
# Excel 转换工具配置文件
import os
import json
import sys
import logging

# 模板文件路径
TEMPLATE_PATH = "template.xlsx"

# 配置文件路径
CONFIG_DIR = os.path.dirname(os.path.abspath(__file__))
# 检测是否为打包环境
if getattr(sys, 'frozen', False):
# PyInstaller打包后的环境
CONFIG_DIR = os.path.dirname(sys.executable)
else:
# 开发环境
CONFIG_DIR = os.path.dirname(os.path.abspath(__file__))

EMPLOYEE_CONFIG_PATH = os.path.join(CONFIG_DIR, "employee_info.json")
COMPANY_CONFIG_PATH = os.path.join(CONFIG_DIR, "company_options.json") # 保留兼容性
BANK_CONFIG_PATH = os.path.join(CONFIG_DIR, "bank_options.json") # 保留兼容性

# 设置日志记录
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(os.path.join(CONFIG_DIR, "config.log")),
logging.StreamHandler()
]
)
logger = logging.getLogger("config")

# 单元格映射关系(源文件位置 -> 目标文件位置)
# 格式:{(源文件sheet索引, 行, 列): (目标文件sheet索引, 行, 列)}
# 注意:源文件行索引为0表示从C3开始的行
@@ -85,15 +106,20 @@ DEFAULT_EMPLOYEE_INFO = [
# 加载员工信息
def load_employee_info():
# 检查是否存在员工信息配置
logger.debug("尝试从 {0} 加载员工信息".format(EMPLOYEE_CONFIG_PATH))
if os.path.exists(EMPLOYEE_CONFIG_PATH):
try:
with open(EMPLOYEE_CONFIG_PATH, 'r', encoding='utf-8') as f:
employee_info = json.load(f)
logger.info("成功加载 {0} 条员工信息".format(len(employee_info)))
return employee_info
except Exception as e:
print(f"加载员工信息出错: {e}")
employee_info = DEFAULT_EMPLOYEE_INFO
logger.error("加载员工信息出错: {0}".format(str(e)))
return DEFAULT_EMPLOYEE_INFO
else:
# 如果不存在,尝试从旧的配置中导入
logger.warning("员工信息配置文件不存在,尝试从旧配置导入")
employee_info = []
company_options = []
bank_options = []
@@ -103,8 +129,9 @@ def load_employee_info():
try:
with open(COMPANY_CONFIG_PATH, 'r', encoding='utf-8') as f:
company_options = json.load(f)
logger.info("从旧配置加载了 {0} 个公司选项".format(len(company_options)))
except Exception as e:
print(f"加载公司配置出错: {e}")
logger.error("加载公司配置出错: {0}".format(str(e)))
# 加载旧的银行配置
if os.path.exists(BANK_CONFIG_PATH):
@@ -123,8 +150,9 @@ def load_employee_info():
"account_holder": ""
})
bank_options = new_bank_options
logger.info("从旧配置加载了 {0} 个银行选项".format(len(bank_options)))
except Exception as e:
print(f"加载银行配置出错: {e}")
logger.error("加载银行配置出错: {0}".format(str(e)))
# 合并旧配置
if bank_options and isinstance(bank_options[0], dict):
@@ -141,21 +169,47 @@ def load_employee_info():
# 保存新的员工信息
save_options(EMPLOYEE_CONFIG_PATH, employee_info)
return employee_info
logger.info("创建了新的员工信息配置,包含 {0} 条记录".format(len(employee_info)))
return employee_info

# 保存选项
def save_options(config_path, options):
try:
# 确保目录存在
config_dir = os.path.dirname(config_path)
if not os.path.exists(config_dir):
os.makedirs(config_dir)
logger.info("创建目录: {0}".format(config_dir))
# 保存前记录当前工作目录和文件路径
logger.debug("当前工作目录: {0}".format(os.getcwd()))
logger.debug("保存配置到: {0}".format(config_path))
# 保存配置
with open(config_path, 'w', encoding='utf-8') as f:
json.dump(options, f, ensure_ascii=False, indent=4)
return True
# 验证保存是否成功
if os.path.exists(config_path):
logger.info("成功保存配置到 {0}".format(config_path))
return True
else:
logger.error("保存失败,文件不存在: {0}".format(config_path))
return False
except Exception as e:
print(f"保存配置出错: {e}")
logger.error("保存配置出错: {0}".format(str(e)), exc_info=True)
return False

# 加载选项
EMPLOYEE_INFO = load_employee_info()
COMPANY_OPTIONS = [info.get("company_name") for info in EMPLOYEE_INFO if info.get("company_name")]
COMPANY_OPTIONS = list(set(COMPANY_OPTIONS)) # 去重
BANK_OPTIONS = [info for info in EMPLOYEE_INFO] # 兼容性:旧代码可能仍使用BANK_OPTIONS
try:
logger.info("开始加载配置...")
EMPLOYEE_INFO = load_employee_info()
COMPANY_OPTIONS = [info.get("company_name") for info in EMPLOYEE_INFO if info.get("company_name")]
COMPANY_OPTIONS = list(set(COMPANY_OPTIONS)) # 去重
BANK_OPTIONS = [info for info in EMPLOYEE_INFO] # 兼容性:旧代码可能仍使用BANK_OPTIONS
logger.info("加载了 {0} 条员工信息, {1} 个公司选项".format(len(EMPLOYEE_INFO), len(COMPANY_OPTIONS)))
except Exception as e:
logger.critical("配置加载失败: {0}".format(str(e)), exc_info=True)
EMPLOYEE_INFO = DEFAULT_EMPLOYEE_INFO
COMPANY_OPTIONS = []
BANK_OPTIONS = []

+ 3
- 3
employee_info.json Ver arquivo

@@ -147,10 +147,10 @@
"account_holder": ""
},
{
"employee_name": "西本智子",
"employee_name": "西本智子1",
"company_name": "SPD株式会社",
"bank_name": "",
"branch_account": "",
"bank_name": "11",
"branch_account": "11",
"account_holder": ""
}
]

+ 2
- 1
main.py Ver arquivo

@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
import os
import sys
import tkinter as tk
@@ -27,7 +28,7 @@ def main():
screen_height = root.winfo_screenheight()
x = (screen_width - window_width) // 2
y = (screen_height - window_height) // 2
root.geometry(f"{window_width}x{window_height}+{x}+{y}")
root.geometry("{0}x{1}+{2}+{3}".format(window_width, window_height, x, y))
# Run the application
root.mainloop()

+ 5
- 5
requirements.txt Ver arquivo

@@ -1,5 +1,5 @@
pandas>=2.0.0
openpyxl>=3.1.0
xlrd>=2.0.0
xlwings>=0.30.0
pyinstaller>=6.0.0
openpyxl>=3.0.0
pandas>=1.0.0
xlrd>=1.0.0
xlwings>=0.20.0
pyinstaller>=4.0.0

Carregando…
Cancelar
Salvar