'''
档案归档是否希望自动根据页数分盒归档?本文讲述能自动分卷来实现,分盒后序号、页号都自动从1开始。能自动单元行加粗。能自动分页。下一步计划完善自动插入分页符的功能。
目前的档案归档是大概250页手工分盒,然后手写页号后生成目录,本程序的目的不是替换手工,而是和手工分卷进行相互印证,也可提前规划分卷情况。
本程序功能的简单描述:
根据提供的表头、行内容,实现自动分页、分卷,自动编序号、自动写入页号。并且,通过在A列是否填入序号来选择是否输入某些行。
程序具体要求(地址可以自己修改):
项目是通过tkinter在C:\\Users\\1\\Desktop\\T1\\目录下实现一个商业级python办公软件"目录生成",目的是只要做好模板,就能根据待复制文件生成结果文件。注意界面要美观,默认字体仿宋(已确定字体可用),字号12号,调整为更漂亮的布局,颜色为黄色系。打开文件框的默认打开位置:"/home/huanghe/Desktop/T1/"或者"C:\\Users\\1\\Desktop\\T1\\"在界面下部显示提示:"自动查找文件夹内"1"为开头的xlsx文件第一个工作表作为模板文件,其A1至G7单元格的内容作为待复制文件,其打印区域设置复制出错时则忽略这个错误。把模板文件的内容套进待复制文件中生成新的结果文件,写入时保持待复制文件原格式然后根据要求修改。界面只有打开位置地址栏、打开文件夹(深黄色)、开始(绿色)两个按钮。如果点击打开文件夹按钮可以选择要处理的文件夹。如果不点击打开文件夹按钮,则直接点击开始按钮时自动选择默认打开位置。界面中复选框"带页号中止",默认选中,如果选中,则F列写入值时判断对当前行的页数,如果页数为1则不变,如果页数大于1的数字,则写入:页数+"-"+(页号+页数-1)。意思是显示文件页号的起和止。
逻辑描述:总要求点击开始按钮后,
把模板文件的内容套进待复制文件中生成新的结果文件,保持待复制文件原格式。并保存在当前文件夹下的新建文件夹中,文件夹的名称取自模板文件表中D2单元格的名字。要求:按照A4的尺寸自动分页,表头是模板文件的前6行,每页都有表头,表头中的共几卷、第几卷根据实际情况改,每页的最后一行是"第*页"这样的页码。模板文件的H列是每行内容的页数,结果文件的F列是据此叠加的页号,页号超过250自动分卷,分卷后序号、页号重新计。如果模板文件的D列是包含"单元"内容,则这一行不写入A列序号和F列页号。自动调整行高。
开始总的主程序,顺序完成模板操作和写入程序,具体如下:
以下要求不可遗漏,如果逻辑有冲突则提示。
模板操作:
首先找模板文件,即自动查找文件夹内"1"为开头的xlsx文件中第1个工作表中"C2、B3、D3、F3、B4、D4、F4、F5"单元格的内容逐个写入表头数组。
从模板文件的第7行H列开始向下读取该列所有内容,作为对应行的页数数组。
变量"xh"初始值为1(就是序号),变量"ym"初始值为1(就是当前页在目录的第几页),变量"卷号"初始值为1(就是卷号),变量"yy"初始值250(就是每卷的最大允许页号),变量"HGhj"初始值为0(就是每页的行高合计)。变量"jh"(就是卷号)。变量"yhhj"初始值为1(就是档案在当前卷的第几页就是页号),变量"yhj"初始值为510(就是每页允许磅值)变量"ymhg"初始值22(就是第几页所在行的行高)
执行"模板操作"后执行写入程序的循环操作。
注意:其中结果文件在多次循环操作中多次修改,最后才通过输出程序正式保存。
写入程序:
每个执行写入程序包含多次"页写入"循环,每个"页写入"的循环包括1次复制复制页、1次表头写入和循环行写入的操作。页写入程序的整体运行逻辑依据:该程序是为了将模板内容分页输出,每页程序都是进行先复制复制页操作,来复制一个新的"复制页"写一次表头然后看是说明行还是序号行按要求逐行写入数据,调整行高,如果在该行吸入后判断该分卷则分卷,该分页则分页,然后重新执行写入程序,还是先复制一个新的"复制页"写一次表头再继续延续之前行从已写入内容的下一行继续执行,直至执行完毕,如果判断已执行则结束写入程序,接着执行输出程序得到结果文件即可。以此实现模板文件的内容全部写入结果文件。
复制复制页:其次找到待复制文件,其第一个工作表中有A1至G7单元格的内容的表格整体复制后作为"复制页"作为临时文件,等待循环替换并按照原格式输出。注意:复制单元格样式时应保留原格式,例如字体、字号、是否加粗、行距、边框、居中、自动换行、合并单元格信息。
表头写入:将表头数组的值写入"复制页"的对应位置单元格,注意保留待复制文件原格式,例如字体、字号、是否加粗、行距、边框、居中、是否自动换行、合并单元格等信息,然后加上细边框。
循环行写入:从模板文件得到的第A列至G列从第7行开始逐行往下开始读取内容,直至把每行读完后入完毕,并记住读取模板文件的第几行,以便下一个写入程序循环。首先每行都执行该行的D列单元判断、A列判断、行高判断、分离判断,不断按照从上到下的顺序将B列至G列逐行写入"复制页"B列至G列从第7行开始逐行往下判断后写入,以此类推把以下每行单元格内容写入。(注意延续上一个"页写入"循环最后写入的行继续向下)
D列单元判断:每行D列单元格的写入内容如果字数小于10个字符且包含"单元"则该行作为一个"说明行",该行不再进行A列判断,仅对应写入A到G列单元内容,该行D列单元格格式设置为宋体12号加粗、左右居中、垂直居中。其对应行的F列不写入,其对应的A列也不写入。注意:该行不参与序号、页码的合计及写入,不参与"序号行"的序号xh和页码yhhj计数。
A列判断:如果模板文件中该行D列单元判断不是"说明行",判断A列的单元格如果是数值,就作为"序号行"进行序号写入"复制页"。每个"序号行"的A列均写入序号变量xh,每写入一行xh自动加1。
每个"序号行"的F列写入的页号就是变量yhhj,每个序号行写入结束变量yhhj就叠加本行的页数,得到的新变量yhhj。B、C、D、E、G列写入对应的模板文件中单元格的值并保持原字体、字号、左右居中、垂直居中、自动换行的格式,然后设置该行A到G列单元边框为细边框。
例如:如果第2卷的第1个"序号行"页数是2,它的页号就是yhhj的初始值1(yhhj)因此其F列写入1,并且变量yhhj增加2等于3,因此下一个"序号行"写入的页号就是新的yhhj,也就是3以此类推。
例如:如果A列的单元格不是数字则自动跳过该行寻找下一行,例如:第1卷模板文件的第7行经D列单元判断为"说明行"则按"说明行"要求写入"复制页"第7行。模板文件的第8行经D列单元判断不是为"说明行"且A列内容是1,判断是数字并且此行H列的页数2,因此将xh的值1正常写入"复制页"第8行A列,B列至E列对照写入,F列写入页号值为1(即yhhj初始值为1)然后yhhj增加2=3,接着判断出模板文件第9、10行的内容不为数字且不是"说明行",则不写入,继续进行下一行判断,找到模板文件的第11行同样不为数字但是符合D列单元判断,则为"说明行"写入"复制页"第9行D列、其中A列和F列为空,继续向下判断,找到模板文件的第12行数字且不是"说明行"时,将其B列至E列作为"序号行"写入到"复制页"的第10行,其A列写入了序号xh值2(xh=xh+1,即不断累加),其页号在F列写入3(因为在上一个"序号行"写入后yhhj已变为3)(注意维持原格文字式),以此类推。
分离判断:首先,每行判断是否模板文件的B列至E列内容的所有行均写入了,如果写入完了,则执行结尾操作,之后跳出该行的分离判断不再进行下一步判断,结束写入程序,开始输出程序。否则,每行判断当页码合计yhhj大于yy,或"1"为开头的xlsx文件中第1个工作表中I列(此列为强制分卷标记)的该行有内容为"分卷"时,则执行分卷操作之后跳出该行分离判断不再进行下一步判断,保留读取模板文件的行号继续循环运行写入程序。否则,如果行高合计HGhj≥yhg磅时或I列(此列为强制分卷标记)的该行有内容为"分页"时,执行分页操作,保留读取模板文件的行号继续循环运行写入程序。
结尾操作:将当前写入行的向下保留wc行,行高25,并将这些行单元格边框设置为细边框,之下的所有行内容删除,并在写入行的向下wc+1行的F列单元格写入"第ym页",替换文中ym为变量"ym"的值,格式为宋体12居中,该行行高为变量ymhg。wc的值为:(yhj-HGhj)/25的结果四舍五入取整数。将此"复制页"写入结果文件的x+1行(按从上到下顺序放入结果文件的第一个工作表)。接着将结果文件中所有的"共 1 卷"、"第 - 卷"内容,分别替换为"共 jh 卷"、"第 jh 卷",其中的jh替换为变量"jh"的值(即最后的卷号就是整体总卷号)。然后结束写入程序执行输出程序。
分卷操作:将当前写入行的向下保留wc行,行高25,并将这些行单元格边框设置为细边框,之下的所有行内容删除,并在写入行的向下wc+1行的F列单元格写入"第ym页",替换文中ym为变量"ym"的值,格式为宋体12居中,该行行高为变量ymhg。wc的值为:(yhj-HGhj)/25的结果四舍五入取整数。然后将"复制页"中"第 - 卷"内容,替换为"第 jh 卷",其中的jh替换为变量"jh"的值,然后将序号"xh"、页号合计yhhj的值和页码"ym"重置为1,重置行高合计HGhj的值为0,卷号变量"jh"值加1。将修改后的"复制页"写入结果文件中原有内容向下一行的位置(按从上到下顺序放入结果文件的第一个工作表)。如此完整一页同时完成了卷号增加和页码页重置。接着进入写入程序的下一次循环。
分页操作:清除最后一个写入行以下的表格内容,包括边框,并在写入行的向下一行的F列单元格写入"第ym页",格式为宋体12居中,该行行高为变量ymhg。替换文中ym为变量"ym"的值,随后页码ym值加1,重置行高合计HGhj的值为0。如此完成一页。每页按从上到下顺序放入结果文件的第一个工作表,(意思是一页放不下了,新生成一页)之后的每一页写入程序的循环应在x+1行开始写入。将此"复制页"写入结果文件的x+1行。即如果之前有写入页则从已写入内容的下一行继续写入。接着进入写入程序的下一次循环。
行高判断:行高DHG,用于通过每行的D列单元格确定行高,算法为每15个字一个级别,英文、数字占半个字,标点占一个字的位置。如该行D单元格字数≤15,行高DHG=15*1+10=25,16≤字数≤30算两行,行高DHG=15*2+10=40,,31≤字数≤45算三行,行高DHG=15*3+10=55。以此类推,单位为磅。
行高BHG用于通过每行的B列单元格确定行高,算法为每6个字一个级别,英文、数字占半个字,标点占一个字的位置。如该行B单元格字数≤12,行高DHG=10*1+10=25,13≤字数≤17算两行,行高DHG=15*2+10=40,,31≤字数≤45算三行,行高DHG=15*3+10=55。以此类推,单位为磅。
行高CHG用于通过每行的C列单元格确定行高,算法为每4个字一个级别,英文、数字占半个字,标点占一个字的位置。如该行B单元格字数≤4,行高DHG=10*1+10=25,5≤字数≤8算两行,行高DHG=15*2+10=40,9≤字数≤12算三行,行高DHG=15*3+10=55。以此类推,单位为磅。
该行的行高HG为DHG、BHG、CHG的最大值,具体调整该行行高,行高合计HGhj为本页内逐行叠加行高HG进行累加的结果。
输出程序:写入程序结束后,无需手动操作,程序自动在选中的文件夹即当前文件夹下自动新建一个文件夹(注意是一个文件夹不是多个文件夹)。该文件夹名称是取自"1"为开头的xlsx模板文件中第1个工作表中"C2"单元格内容。将处理结束的结果文件命名为:待复制文件名称(不要扩展名,例如"档案目录")+(+"1"为开头的xlsx模板文件中第1个工作表中"C2"单元格内容+)。然后另存进中新建的文件夹,如果有名字重复的文件夹,则在文件夹名后边增加序号。然后,对保存的结果文件插入分页符,除了最后一个第几页之外,在所有的第几页下插入分页符,
程序生成后逐条核对要求是否符合。
'''
import os # 文件路径操作
import tkinter as tk # GUI框架
from tkinter import ttk, filedialog, messagebox, scrolledtext # GUI组件
from openpyxl import load_workbook, Workbook # Excel操作
from openpyxl.styles import Font, Alignment, Border, Side, PatternFill # Excel样式
from openpyxl.worksheet.pagebreak import Break # Excel分页符
from openpyxl.utils import get_column_letter # Excel列字母转换
import copy # 深拷贝
import re # 正则表达式
import threading # 多线程处理
from datetime import datetime # 时间处理
import time # 时间相关
import math # 数学计算
# 定义专业目录生成器类
class ProfessionalDirectoryGenerator:
"""专业级目录生成器 - 商业级办公软件"""
def __init__(self, root):
"""初始化目录生成器"""
# 设置根窗口属性
self.root = root
self.root.title("审计目录分页分卷算页码 v6.0")
self.root.geometry("1000x200")
# 定义黄色系配色方案
self.colors = {
'primary_bg': '#FFFDE7', # 主背景色
'secondary_bg': '#FFF9C4', # 次背景色
'card_bg': '#FFF59D', # 卡片背景
'accent_yellow': '#FFD54F', # 强调黄色
'accent_orange': '#FFB74D', # 橙色
'success_green': '#8BC34A', # 成功绿色
'warning_orange': '#FF9800', # 警告橙色
'error_red': '#F44336', # 错误红色
'text_dark': '#5D4037', # 深色文字
'text_light': '#FFFFFF', # 浅色文字
'entry_bg': '#FFFFFF', # 输入框背景
'border_color': '#FFB300', # 边框色
'button_yellow': '#FFC107', # 黄色按钮
'button_green': '#4CAF50', # 绿色按钮
'status_bar': '#FFF176', # 状态栏
}
# 定义字体配置
self.fonts = {
'title': ("Fangsong Ti", 10, "bold"), # 标题字体
'heading': ("Fangsong Ti", 10, "bold"), # 标题字体
'normal': ("Fangsong Ti", 10), # 普通字体
'small': ("Fangsong Ti", 10), # 小字体
'mono': ("Consolas", 10), # 等宽字体
}
# 定义状态变量
self.is_processing = False # 是否正在处理
self.cancel_requested = False # 是否请求取消
self.show_page_range = tk.BooleanVar(value=True) # 新增:显示页号起止复选框
# 设置默认路径
if os.name == 'posix':
self.default_path = "/home/huanghe/Desktop/T1/" # Linux路径
else:
self.default_path = "C:\\Users\\1\\Desktop\\T1\\" # Windows路径
# 如果默认路径不存在,使用桌面路径
if not os.path.exists(self.default_path):
if os.name == 'posix':
self.default_path = os.path.expanduser("~/Desktop") # Linux桌面
else:
self.default_path = os.path.join(os.path.expanduser("~"), "Desktop") # Windows桌面
# 设置用户界面
self.setup_ui()
def setup_ui(self):
"""设置用户界面"""
# 创建主容器框架
main_container = tk.Frame(self.root, bg=self.colors['primary_bg'])
main_container.pack(fill=tk.BOTH, expand=True, padx=20, pady=20)
# 创建主内容区域
content_frame = tk.Frame(main_container, bg=self.colors['primary_bg'])
content_frame.pack(fill=tk.BOTH, expand=True)
# 创建左侧控制面板
left_panel = tk.Frame(content_frame, bg=self.colors['primary_bg'])
left_panel.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0, 15))
# 创建工作目录设置框架
dir_frame = self.create_styled_frame(left_panel, "📂 工作目录设置")
# 创建路径输入框架
path_input_frame = tk.Frame(dir_frame, bg=self.colors['secondary_bg'])
path_input_frame.pack(fill=tk.X, padx=20, pady=20)
# 创建路径标签
tk.Label(
path_input_frame,
text="工作目录:",
font=self.fonts['normal'],
bg=self.colors['secondary_bg'],
fg=self.colors['text_dark']
).pack(side=tk.LEFT, padx=(0, 10))
# 创建路径变量和输入框
self.path_var = tk.StringVar(value=self.default_path)
self.path_entry = tk.Entry(
path_input_frame,
textvariable=self.path_var,
font=self.fonts['normal'],
bg=self.colors['entry_bg'],
fg='#000000',
width=45
)
self.path_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 10))
# 创建浏览按钮(深黄色)
self.browse_btn = tk.Button(
path_input_frame,
text="📂 浏览文件夹",
command=self.browse_folder,
font=self.fonts['normal'],
bg='#FFB300', # 深黄色
fg=self.colors['text_dark'],
activebackground='#FFA000',
activeforeground=self.colors['text_dark'],
relief=tk.RAISED,
bd=2,
padx=20,
pady=5,
cursor="hand2"
)
self.browse_btn.pack(side=tk.RIGHT)
# 创建操作控制区
control_frame = self.create_styled_frame(left_panel, "🎮 操作控制")
# 创建选项框架
options_frame = tk.Frame(control_frame, bg=self.colors['card_bg'])
options_frame.pack(pady=10)
# 创建复选框:显示页号起止
self.range_checkbox = tk.Checkbutton(
options_frame,
text="显示页号起止",
variable=self.show_page_range,
font=self.fonts['normal'],
bg=self.colors['card_bg'],
fg=self.colors['text_dark'],
selectcolor=self.colors['secondary_bg']
)
self.range_checkbox.pack(side=tk.LEFT, padx=10)
# 创建按钮容器
button_container = tk.Frame(control_frame, bg=self.colors['card_bg'])
button_container.pack(pady=30)
# 创建开始按钮(绿色)
self.start_btn = tk.Button(
button_container,
text=" 开始",
command=self.start_processing,
font=self.fonts['normal'],
bg=self.colors['button_green'],
fg=self.colors['text_light'],
activebackground='#45a049',
activeforeground=self.colors['text_light'],
relief=tk.RAISED,
bd=2,
padx=30,
pady=10,
cursor="hand2"
)
self.start_btn.pack(pady=10)
# 创建底部信息栏
bottom_frame = tk.Frame(main_container, bg=self.colors['status_bar'], height=60)
bottom_frame.pack(fill=tk.X, pady=(20, 0))
bottom_frame.pack_propagate(False)
info_text = "提示:自动查找文件夹内'1'开头的.xlsx文件作为模板文件,使用其A1至G7单元格内容作为待复制文件内容"
info_label = tk.Label(
bottom_frame,
text=info_text,
font=self.fonts['small'],
bg=self.colors['status_bar'],
fg=self.colors['text_dark'],
pady=20,
wraplength=600
)
info_label.pack()
# 绑定快捷键
self.root.bind('<Return>', lambda e: self.start_processing())
self.root.bind('<Escape>', lambda e: self.stop_processing())
# 记录初始日志
self.log_message("系统初始化完成", "info")
self.log_message(f"默认工作目录: {self.default_path}", "info")
def create_styled_frame(self, parent, title):
"""创建带标题的装饰框"""
# 创建框架
frame = tk.Frame(parent, bg=self.colors['card_bg'], relief=tk.RAISED, bd=2)
frame.pack(fill=tk.BOTH, expand=True, pady=(0, 15))
# 创建标题标签
title_label = tk.Label(
frame,
text=title,
font=self.fonts['heading'],
bg=self.colors['accent_yellow'],
fg=self.colors['text_dark'],
padx=20,
pady=10
)
title_label.pack(anchor=tk.W, fill=tk.X)
return frame
def browse_folder(self):
"""浏览文件夹"""
# 打开文件夹选择对话框
folder_path = filedialog.askdirectory(
initialdir=self.path_var.get(),
title="选择工作文件夹"
)
# 如果选择了路径,则更新路径变量并记录日志
if folder_path:
self.path_var.set(folder_path)
self.log_message(f"工作目录已设置为: {folder_path}", "info")
def clear_log(self):
"""清空日志(简化版,移除日志文本框)"""
# 简化版本,什么都不做
pass
def log_message(self, message, level="info"):
"""记录日志消息(简化版,移除日志文本框)"""
# 简化版本,只打印到控制台
timestamp = datetime.now().strftime("%H:%M:%S")
level_icons = {
"info": "ℹ️",
"success": "✅",
"warning": "⚠️",
"error": "❌",
"debug": "🔍"
}
icon = level_icons.get(level, "•")
print(f"[{timestamp}] {icon} {message}")
def update_status(self, message, progress=None):
"""更新状态(简化版,移除状态标签和进度条)"""
# 简化版本,只打印消息
print(f"状态更新: {message}")
def update_stats(self, files=0, rows=0, pages=0, volumes=0):
"""更新统计信息(简化版,移除统计信息框架)"""
# 简化版本,什么都不做
pass
def start_processing(self):
"""开始处理"""
# 如果已经在处理中,显示警告
if self.is_processing:
messagebox.showwarning("警告", "已经在处理中,请等待当前任务完成。")
return
# 获取工作目录
work_dir = self.path_var.get().strip()
# 如果没有指定工作目录,使用默认路径
if not work_dir:
work_dir = self.default_path
self.path_var.set(work_dir)
# 检查工作目录是否存在
if not os.path.exists(work_dir):
messagebox.showerror("错误", f"指定的文件夹不存在!\n{work_dir}")
return
# 重置状态变量
self.is_processing = True
self.cancel_requested = False
# 禁用开始按钮
self.start_btn.config(state=tk.DISABLED)
# 记录开始处理的日志
self.log_message("开始目录生成处理", "info")
self.update_status("正在查找文件...", 10)
# 在工作线程中启动处理
thread = threading.Thread(target=self.process_directory, args=(work_dir,))
thread.daemon = True
thread.start()
def stop_processing(self):
"""停止处理"""
# 如果正在处理,设置取消标志
if self.is_processing:
self.cancel_requested = True
self.log_message("用户请求停止处理", "warning")
def complete_processing(self, success=True):
"""完成处理"""
def update():
# 重置处理状态
self.is_processing = False
# 启用开始按钮
self.start_btn.config(state=tk.NORMAL)
# 根据处理结果更新状态
if success and not self.cancel_requested:
self.log_message("处理完成", "success")
elif self.cancel_requested:
self.log_message("用户请求停止处理", "warning")
else:
self.log_message("处理失败", "error")
# 在主线程中完成处理
self.root.after(0, update)
def process_directory(self, work_dir):
"""处理目录"""
# 记录开始时间
start_time = time.time()
try:
# 记录处理开始日志
self.log_message(f"开始处理目录: {work_dir}", "info")
self.update_status("正在查找文件...", 10)
# 查找模板文件
template_files = []
# 遍历工作目录中的文件
for file in os.listdir(work_dir):
# 只处理.xlsx文件
if file.lower().endswith('.xlsx'):
file_path = os.path.join(work_dir, file)
# 如果文件名以'1'开头,则为模板文件
if file.startswith('1'):
template_files.append(file_path)
# 记录找到的文件数量
self.log_message(f"找到模板文件: {len(template_files)}个", "success")
# 检查是否有模板文件
if not template_files:
self.log_message("错误: 未找到以'1'开头的模板文件!", "error")
self.complete_processing(False)
return
# 处理每个模板文件
success_count = 0
total_templates = len(template_files)
for i, template_file in enumerate(template_files):
# 检查是否请求取消
if self.cancel_requested:
break
# 计算当前处理进度
current = i + 1
progress = 20 + (current / total_templates * 70)
# 更新状态和日志
self.update_status(f"处理文件 {current}/{total_templates}...", progress)
self.log_message(
f"处理文件 {current}: {os.path.basename(template_file)}",
"info")
try:
# 处理单个模板文件
result_file = self.process_single_template(template_file, work_dir)
if result_file:
success_count += 1
self.log_message(f"✓ 成功生成: {os.path.basename(result_file)}", "success")
except Exception as e:
self.log_message(f"✗ 处理失败: {str(e)}", "error")
# 计算处理耗时
elapsed_time = int(time.time() - start_time)
# 根据处理结果记录日志
if self.cancel_requested:
self.log_message(f"处理取消,用时 {elapsed_time}秒,成功生成 {success_count} 个文件", "warning")
else:
self.log_message(f"处理完成,用时 {elapsed_time}秒,成功生成 {success_count} 个文件", "success")
self.update_stats(files=success_count)
# 完成处理
self.complete_processing(success_count > 0)
except Exception as e:
# 记录错误日志
self.log_message(f"处理过程中出现未预期错误: {str(e)}", "error")
self.complete_processing(False)
def process_single_template(self, template_file, work_dir):
"""处理单个模板文件"""
try:
# 1. 模板操作:加载模板文件
template_wb = load_workbook(template_file, data_only=True)
template_ws = template_wb.active
# 读取表头:从特定单元格读取内容
header_cells = ['C2', 'B3', 'D3', 'F3', 'B4', 'D4', 'F4', 'F5']
header_array = []
for cell_addr in header_cells:
cell = template_ws[cell_addr]
header_array.append(cell.value if cell.value is not None else "")
# 读取页数数组:从H列第7行开始读取,确保每个模板行都有对应的页数
page_numbers = []
for row in range(7, template_ws.max_row + 1):
cell_value = template_ws[f'H{row}'].value
try:
page_num = int(round(float(cell_value))) if cell_value is not None else 0
page_numbers.append(max(0, page_num)) # 确保不为负数
except:
page_numbers.append(0)
# 读取强制分卷标记:从I列读取
force_breaks = {}
force_page_breaks = {}
for r in range(7, template_ws.max_row + 1):
cell_value = template_ws.cell(row=r, column=9).value # I列
if cell_value is not None and str(cell_value).strip():
cell_str = str(cell_value).strip()
if cell_str == "分卷":
force_breaks[r] = cell_str
elif cell_str == "分页":
force_page_breaks[r] = cell_str
# 获取项目名称:从C2单元格读取(用于结果文件名)
project_name = str(template_ws['C2'].value or "项目目录").strip()
# 获取文件夹名称:从D2单元格读取
folder_name = str(template_ws['D2'].value or project_name).strip()
# 创建"待复制文件":从模板的A1:G7区域创建
source_wb = Workbook()
source_ws = source_wb.active
source_ws.title = "目录"
# 从模板复制A1:G7区域到源工作表
for row in range(1, 8): # 1 to 7
for col in range(1, 8): # A to G
source_cell = template_ws.cell(row=row, column=col)
target_cell = source_ws.cell(row=row, column=col)
# 复制值
target_cell.value = source_cell.value
# 复制样式
if source_cell.has_style:
target_cell.font = copy.copy(source_cell.font)
target_cell.border = copy.copy(source_cell.border)
target_cell.fill = copy.copy(source_cell.fill)
target_cell.number_format = source_cell.number_format
target_cell.protection = copy.copy(source_cell.protection)
target_cell.alignment = copy.copy(source_cell.alignment)
# 复制合并单元格
for merged_range in template_ws.merged_cells.ranges:
# 检查是否在A1:G7范围内
if merged_range.min_row <= 7 and merged_range.max_row <= 7 and \
merged_range.min_col <= 7 and merged_range.max_col <= 7:
source_ws.merge_cells(str(merged_range))
# 复制列宽
for col in range(1, 8): # A to G
col_letter = get_column_letter(col)
if col_letter in template_ws.column_dimensions:
source_ws.column_dimensions[col_letter].width = template_ws.column_dimensions[col_letter].width
# 复制行高
for row in range(1, 8): # 1 to 7
if row in template_ws.row_dimensions:
source_ws.row_dimensions[row].height = template_ws.row_dimensions[row].height
# 创建结果工作簿
result_wb = Workbook()
result_ws = result_wb.active
result_ws.title = "目录"
# 执行写入程序
output_path = self.execute_writing_program(
template_ws=template_ws,
source_ws=source_ws,
result_ws=result_ws,
header_array=header_array,
page_numbers=page_numbers,
force_breaks=force_breaks,
force_page_breaks=force_page_breaks,
project_name=project_name,
folder_name=folder_name,
source_file=template_file,
work_dir=work_dir,
result_wb=result_wb
)
return output_path
except Exception as e:
raise Exception(f"处理模板文件失败: {str(e)}")
def execute_writing_program(self, template_ws, source_ws, result_ws, header_array,
page_numbers, force_breaks, force_page_breaks, project_name, folder_name, source_file,
work_dir, result_wb):
"""执行写入程序 - 核心逻辑(修正版)"""
try:
# 初始化变量
xh = 1 # 序号
ym = 1 # 目录的第几页
jh = 1 # 当前卷号
zjh = 1 # 总卷号
yy = 250 # 每卷的最大允许页号
HGhj = 0 # 每页的行高合计
yhhj = 1 # 档案在当前卷的第几页就是页号
yhj = 462 # 每页允许磅值
self.ymhg = 15 # 页码的行高 - 修复:定义为实例变量
# 准备模板数据
template_data = []
for row in range(7, template_ws.max_row + 1):
row_data = []
for col in range(1, 8): # A到G列
cell_value = template_ws.cell(row=row, column=col).value
row_data.append(cell_value)
template_data.append(row_data)
total_rows = len(template_data)
self.log_message(f"读取模板数据完成,共 {total_rows} 行", "info")
# 先分析所有行的类型:说明行/序号行/其他
row_types = []
for i, row_data in enumerate(template_data):
d_content = str(row_data[3] or "").strip() # D列
is_unit_row = self.is_unit_row(d_content)
if is_unit_row:
# 优先判断为说明行
row_types.append(('unit', i))
else:
a_content = row_data[0]
is_number_row = self.is_number_row(a_content)
if is_number_row:
row_types.append(('number', i))
else:
row_types.append(('other', i))
# 过滤掉其他类型的行,只保留说明行和序号行
filtered_rows = [(rtype, idx) for rtype, idx in row_types if rtype in ['unit', 'number']]
# 写入程序主循环
template_index = 0 # 模板数据索引
result_start_row = 1 # 结果文件开始行
rows_written = 0 # 已写入行数
pages_written = 0 # 已写入页数
volumes_written = 1 # 已写入卷数
# 主循环:处理所有模板数据
while template_index < len(filtered_rows):
if self.cancel_requested:
break
self.log_message(f"开始第 {pages_written + 1} 页写入", "info")
# 1. 复制复制页
current_page_ws = self.create_copy_page(source_ws)
# 2. 表头写入
self.write_header_to_page(current_page_ws, header_array)
# 3. 初始化当前页的写入位置
current_page_row = 7 # 从第7行开始写入
# 当前页的变量
current_page_HGhj = 0
# 循环行写入
while template_index < len(filtered_rows):
if self.cancel_requested:
break
row_type, original_idx = filtered_rows[template_index]
row_data = template_data[original_idx]
template_row_num = original_idx + 7
if row_type == 'unit':
# 说明行处理
self.write_unit_row(current_page_ws, current_page_row, str(row_data[3]))
# 计算行高并累计(参与行高判断)
row_height = self.calculate_row_height(row_data)
current_page_ws.row_dimensions[current_page_row].height = row_height
current_page_HGhj += row_height
HGhj += row_height
# 分离判断(参与分离判断)
force_break = force_breaks.get(template_row_num, "")
force_page_break = force_page_breaks.get(template_row_num, "") # 使用传入的参数
# 检查是否写完所有数据(结尾操作)
if template_index == len(filtered_rows) - 1:
# 结尾操作
wc = self.calculate_wc(yhj, HGhj)
self.perform_end_operation(
current_page_ws, current_page_row,
wc, ym, jh
)
# 复制当前页到结果文件
self.copy_page_to_result(current_page_ws, result_ws, result_start_row)
result_start_row += self.get_page_height(current_page_ws)
pages_written += 1
template_index += 1
break
# 检查是否需要强制分卷
elif force_break == "分卷": # 修改:检查是否为分卷
# 分卷操作
wc = self.calculate_wc(yhj, HGhj)
self.perform_volume_break(
current_page_ws, current_page_row,
wc, ym, jh
)
# 复制当前页到结果文件
self.copy_page_to_result(current_page_ws, result_ws, result_start_row)
result_start_row += self.get_page_height(current_page_ws)
pages_written += 1
volumes_written += 1
# 重置变量
xh = 1
yhhj = 1
ym = 1
jh += 1
zjh = jh # 更新总卷号
HGhj = 0
template_index += 1
break
# 检查是否需要强制分页
elif force_page_break == "分页": # 新增:检查是否为分页
# 分页操作
wc = self.calculate_wc(yhj, HGhj)
self.perform_page_break(
current_page_ws, current_page_row,
wc, ym, jh
)
# 复制当前页到结果文件
self.copy_page_to_result(current_page_ws, result_ws, result_start_row)
result_start_row += self.get_page_height(current_page_ws)
pages_written += 1
# 更新变量
ym += 1
HGhj = 0
template_index += 1
break
# 检查是否需要自动分卷
elif yhhj > yy:
# 分卷操作
wc = self.calculate_wc(yhj, HGhj)
self.perform_volume_break(
current_page_ws, current_page_row,
wc, ym, jh
)
# 复制当前页到结果文件
self.copy_page_to_result(current_page_ws, result_ws, result_start_row)
result_start_row += self.get_page_height(current_page_ws)
pages_written += 1
volumes_written += 1
# 重置变量
xh = 1
yhhj = 1
ym = 1
jh += 1
zjh = jh # 更新总卷号
HGhj = 0
template_index += 1
break
# 检查是否需要自动分页
elif HGhj >= yhj:
# 计算wc值
wc = self.calculate_wc(yhj, HGhj)
# 分页操作 - 传递计算后的wc值
self.perform_page_break(
current_page_ws, current_page_row,
wc, ym, jh
)
# 复制当前页到结果文件
self.copy_page_to_result(current_page_ws, result_ws, result_start_row)
result_start_row += self.get_page_height(current_page_ws)
pages_written += 1
# 更新变量
ym += 1
HGhj = 0
template_index += 1
break
# 继续当前页
current_page_row += 1
rows_written += 1
template_index += 1
elif row_type == 'number':
# 序号行处理
# 获取当前行对应的页数 - 使用original_idx作为索引
page_increment = 0
if original_idx < len(page_numbers):
page_increment = page_numbers[original_idx]
# 计算实际应该显示的页号(当前累计值)
actual_page_number = yhhj
# 序号行处理 - 传递页数增量信息
self.write_number_row(
current_page_ws, current_page_row, row_data,
xh, actual_page_number, page_increment # 传递累加后的页号和页数增量
)
# 更新序号 - 只在序号行时更新
xh += 1
rows_written += 1
# 计算行高
row_height = self.calculate_row_height(row_data)
# 设置行高并累计
current_page_ws.row_dimensions[current_page_row].height = row_height
current_page_HGhj += row_height
HGhj += row_height
# 累加页号到累计值中 - 直接使用original_idx作为索引
if original_idx < len(page_numbers):
yhhj += page_increment
# 分离判断
force_break = force_breaks.get(template_row_num, "")
force_page_break = force_page_breaks.get(template_row_num, "") # 使用传入的参数
# 检查是否写完所有数据
if template_index == len(filtered_rows) - 1:
# 结尾操作
wc = self.calculate_wc(yhj, HGhj)
self.perform_end_operation(
current_page_ws, current_page_row,
wc, ym, jh
)
# 复制当前页到结果文件
self.copy_page_to_result(current_page_ws, result_ws, result_start_row)
result_start_row += self.get_page_height(current_page_ws)
pages_written += 1
template_index += 1
break
# 检查是否需要强制分卷
elif force_break == "分卷": # 修改:检查是否为分卷
# 分卷操作
wc = self.calculate_wc(yhj, HGhj)
self.perform_volume_break(
current_page_ws, current_page_row,
wc, ym, jh
)
# 复制当前页到结果文件
self.copy_page_to_result(current_page_ws, result_ws, result_start_row)
result_start_row += self.get_page_height(current_page_ws)
pages_written += 1
volumes_written += 1
# 重置变量
xh = 1
yhhj = 1
ym = 1
jh += 1
zjh = jh # 更新总卷号
HGhj = 0
template_index += 1
break
# 检查是否需要强制分页
elif force_page_break == "分页": # 新增:检查是否为分页
# 分页操作
wc = self.calculate_wc(yhj, HGhj)
self.perform_page_break(
current_page_ws, current_page_row,
wc, ym, jh
)
# 复制当前页到结果文件
self.copy_page_to_result(current_page_ws, result_ws, result_start_row)
result_start_row += self.get_page_height(current_page_ws)
pages_written += 1
# 更新变量
ym += 1
HGhj = 0
template_index += 1
break
# 检查是否需要自动分卷
elif yhhj > yy:
# 分卷操作
wc = self.calculate_wc(yhj, HGhj)
self.perform_volume_break(
current_page_ws, current_page_row,
wc, ym, jh
)
# 复制当前页到结果文件
self.copy_page_to_result(current_page_ws, result_ws, result_start_row)
result_start_row += self.get_page_height(current_page_ws)
pages_written += 1
volumes_written += 1
# 重置变量
xh = 1
yhhj = 1
ym = 1
jh += 1
zjh = jh # 更新总卷号
HGhj = 0
template_index += 1
break
# 检查是否需要自动分页
elif HGhj >= yhj:
# 计算wc值
wc = self.calculate_wc(yhj, HGhj)
# 分页操作 - 传递计算后的wc值
self.perform_page_break(
current_page_ws, current_page_row,
wc, ym, jh
)
# 复制当前页到结果文件
self.copy_page_to_result(current_page_ws, result_ws, result_start_row)
result_start_row += self.get_page_height(current_page_ws)
pages_written += 1
# 更新变量
ym += 1
HGhj = 0
template_index += 1
break
else:
# 继续当前页
current_page_row += 1
template_index += 1
else:
# 其他类型行,跳过
template_index += 1
# 更新进度
if template_index % 10 == 0:
progress = 30 + (template_index / len(filtered_rows) * 60)
self.update_status(f"写入数据: {template_index}/{len(filtered_rows)}", progress)
self.update_stats(0, rows_written, pages_written, volumes_written)
# 替换总卷号(在所有数据处理完成后)
# 遍历整个结果工作表,替换所有zjh占位符为总卷号
for row in result_ws.iter_rows():
for cell in row:
if cell.value and isinstance(cell.value, str):
new_value = str(cell.value)
# 替换zjh(总卷号,不区分大小写)
if 'zjh' in new_value.lower():
new_value = new_value.replace('zjh', str(zjh))
new_value = new_value.replace('ZJH', str(zjh))
new_value = new_value.replace('Zjh', str(zjh))
if new_value != str(cell.value):
cell.value = new_value
# 替换 "第 - 卷" 为 "第 jh 卷"
if '第 - 卷' in new_value:
new_value = new_value.replace('第 - 卷', f'第 {jh} 卷')
# 替换 "共 1 卷" 为 "共 zjh 卷"
if '共 1 卷' in new_value:
new_value = new_value.replace('共 1 卷', f'共 {zjh} 卷')
if new_value != str(cell.value):
cell.value = new_value
# 更新统计
self.update_stats(0, rows_written, pages_written, volumes_written)
# 保存文件
output_path = self.save_output_file(work_dir, folder_name, project_name, source_file, result_wb)
self.log_message(f"写入完成: {rows_written}行,{pages_written}页,{volumes_written}卷", "success")
return output_path
except Exception as e:
raise Exception(f"写入程序执行失败: {str(e)}")
def replace_volume_numbers_fixed(self, worksheet, current_jh, total_jh):
"""替换卷号 - 修正版(确保zjh被替换)"""
for row in worksheet.iter_rows():
for cell in row:
if cell.value and isinstance(cell.value, str):
# 替换jh和zjh(不区分大小写)
new_value = str(cell.value)
# 先替换zjh(总卷号)
if 'zjh' in new_value.lower():
new_value = new_value.replace('zjh', str(total_jh))
new_value = new_value.replace('ZJH', str(total_jh))
new_value = new_value.replace('Zjh', str(total_jh))
# 然后替换jh(当前卷号)- 注意避免替换已替换的zjh部分
if 'jh' in new_value.lower():
# 使用正则表达式确保只替换单独的jh
import re
new_value = re.sub(r'(?<!z)(?<!Z)jh', str(current_jh), new_value, flags=re.IGNORECASE)
if new_value != str(cell.value):
cell.value = new_value
def is_unit_row(self, d_content):
"""判断是否为说明行"""
if not d_content:
return False
# 字数小于10个字符且包含"单元"
char_count = len(d_content)
if char_count < 10 and "单元" in d_content:
return True
return False
def is_number_row(self, a_content):
"""判断是否为数字行"""
if a_content is None:
return False
try:
float(str(a_content))
return True
except:
return False
def create_copy_page(self, source_ws):
"""创建复制页(安全复制样式和列宽)"""
# 创建新的工作簿和工作表
copy_wb = Workbook()
copy_ws = copy_wb.active
# 复制单元格内容和样式
for row in source_ws.iter_rows():
for source_cell in row:
target_cell = copy_ws.cell(
row=source_cell.row,
column=source_cell.column,
value=source_cell.value
)
# 安全复制样式(避免StyleProxy问题)
self.safe_copy_style(source_cell, target_cell)
# 复制合并单元格
for merged_range in source_ws.merged_cells.ranges:
copy_ws.merge_cells(str(merged_range))
# 复制列宽(修正版)
for col in range(1, source_ws.max_column + 1):
col_letter = get_column_letter(col)
if col_letter in source_ws.column_dimensions:
source_col_dim = source_ws.column_dimensions[col_letter]
# 直接设置目标列的宽度
copy_ws.column_dimensions[col_letter].width = source_col_dim.width
# 复制行高
for row in range(1, source_ws.max_row + 1):
if row in source_ws.row_dimensions:
source_row_dim = source_ws.row_dimensions[row]
if source_row_dim.height is not None:
copy_ws.row_dimensions[row].height = source_row_dim.height
# 复制打印区域,如果有的话
try:
if source_ws.print_area:
copy_ws.print_area = source_ws.print_area
except:
# 忽略打印区域复制错误
pass
return copy_ws
def safe_copy_style(self, source_cell, target_cell):
"""安全复制样式(避免StyleProxy和MergedCell问题)"""
try:
# 检查是否为合并单元格
if hasattr(source_cell, 'merged') and source_cell.merged:
return
# 复制字体
if hasattr(source_cell, 'font') and source_cell.font:
try:
target_cell.font = Font(
name=source_cell.font.name,
size=source_cell.font.size,
bold=source_cell.font.bold,
italic=source_cell.font.italic,
vertAlign=source_cell.font.vertAlign,
underline=source_cell.font.underline,
strike=source_cell.font.strike,
color=source_cell.font.color
)
except:
# 如果复制失败,使用默认字体
target_cell.font = Font(name='宋体', size=11)
# 复制对齐方式
if hasattr(source_cell, 'alignment') and source_cell.alignment:
try:
target_cell.alignment = Alignment(
horizontal=source_cell.alignment.horizontal,
vertical=source_cell.alignment.vertical,
text_rotation=source_cell.alignment.textRotation,
wrap_text=source_cell.alignment.wrapText,
shrink_to_fit=source_cell.alignment.shrinkToFit,
indent=source_cell.alignment.indent
)
except:
target_cell.alignment = Alignment(horizontal='left', vertical='center')
# 复制边框
if hasattr(source_cell, 'border') and source_cell.border:
try:
border = Border(
left=Side(style=source_cell.border.left.style,
color=source_cell.border.left.color),
right=Side(style=source_cell.border.right.style,
color=source_cell.border.right.color),
top=Side(style=source_cell.border.top.style,
color=source_cell.border.top.color),
bottom=Side(style=source_cell.border.bottom.style,
color=source_cell.border.bottom.color)
)
target_cell.border = border
except:
pass
# 复制填充
if hasattr(source_cell, 'fill') and source_cell.fill:
try:
target_cell.fill = PatternFill(
fill_type=source_cell.fill.fill_type,
start_color=source_cell.fill.start_color,
end_color=source_cell.fill.end_color
)
except:
pass
except Exception as e:
# 捕获所有异常,确保即使遇到合并单元格也能继续执行
pass
def write_header_to_page(self, worksheet, header_array):
"""将表头写入复制页"""
header_mapping = [
('C2', 0), ('B3', 1), ('D3', 2), ('F3', 3),
('B4', 4), ('D4', 5), ('F4', 6), ('F5', 7)
]
for cell_addr, idx in header_mapping:
if idx < len(header_array):
cell = worksheet[cell_addr]
# 保持原格式,只更新值
cell.value = header_array[idx]
def write_unit_row(self, worksheet, row, d_content):
"""写入说明行"""
# D列内容
cell_d = worksheet.cell(row=row, column=4)
cell_d.value = d_content
# 从模板的第7行获取D列的原始格式
ref_cell = worksheet.cell(row=7, column=4)
if ref_cell.font:
cell_d.font = copy.copy(ref_cell.font)
if ref_cell.alignment:
cell_d.alignment = copy.copy(ref_cell.alignment)
# 添加细边框
cell_d.border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)
# A列和F列为空
worksheet.cell(row=row, column=1, value=None)
worksheet.cell(row=row, column=6, value=None)
# B列、C列、E列和G列也设为空
worksheet.cell(row=row, column=2, value=None)
worksheet.cell(row=row, column=3, value=None)
worksheet.cell(row=row, column=5, value=None)
worksheet.cell(row=row, column=7, value=None)
# 行高由主循环计算并设置,这里不再设置
# 为A到G列设置细边框和自动换行
for col in range(1, 8): # A到G列
cell = worksheet.cell(row=row, column=col)
cell.font = Font(name='宋体', size=12, bold=True)
cell.border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)
# 设置自动换行
if cell.alignment:
cell.alignment = Alignment(
horizontal=cell.alignment.horizontal,
vertical=cell.alignment.vertical,
wrap_text=True
)
else:
cell.alignment = Alignment(horizontal='center', vertical='center', wrap_text=True)
def write_number_row(self, worksheet, row, row_data, xh, actual_page_number, page_increment=0):
"""写入序号行 - 修正版"""
# A列:序号
cell_a = worksheet.cell(row=row, column=1)
cell_a.value = xh
# 设置A列格式
cell_a.font = Font(name='宋体', size=11)
cell_a.alignment = Alignment(horizontal='center', vertical='center', wrap_text=True)
cell_a.border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)
# B-G列:内容(从row_data[1]到row_data[6],对应B、C、D、E、F、G列)
# 注意:row_data[0]是模板的A列内容(已经处理为序号),这里从B列开始
# 修复:正确的列映射
for col in range(2, 8): # B到G列(2-7)
data_index = col - 1 # 因为A列已经单独处理,所以从索引0开始对应B列
if data_index < len(row_data): # 确保数据索引不超出范围
value = row_data[data_index] # col-1因为row_data包含A到G列
cell = worksheet.cell(row=row, column=col)
cell.value = value
# 设置对应列格式
if col == 2: # B列
cell.font = Font(name='宋体', size=10)
else: # C, D, E, F, G列
cell.font = Font(name='宋体', size=11)
cell.alignment = Alignment(horizontal='center', vertical='center', wrap_text=True)
cell.border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)
# F列特殊处理:页号 - 使用实际累加的页号
cell_f = worksheet.cell(row=row, column=6)
# 根据"显示页号起止"复选框状态决定F列的值
if self.show_page_range.get(): # 如果复选框被选中
# 将页数增量转换为整数,避免小数点
page_inc_int = int(round(page_increment)) if page_increment else 0
if page_inc_int > 1:
# 计算终止页号(必须为正整数)
end_page = int(actual_page_number) + page_inc_int - 1
cell_f.value = f"{int(actual_page_number)}-{end_page}"
else:
cell_f.value = int(actual_page_number)
else:
cell_f.value = int(actual_page_number)
# 设置F列格式
cell_f.font = Font(name='宋体', size=11)
cell_f.alignment = Alignment(horizontal='center', vertical='center', wrap_text=True)
cell_f.border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)
# 确保G列(第7列)也有内容和格式
if 7 <= len(row_data): # 确保有G列数据
cell_g = worksheet.cell(row=row, column=7)
if cell_g.value is None: # 如果还没设置值
cell_g.value = row_data[6] # G列数据
cell_g.font = Font(name='宋体', size=11)
cell_g.alignment = Alignment(horizontal='center', vertical='center', wrap_text=True)
cell_g.border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)
# 为所有单元格确保边框和自动换行
for col in range(1, 8): # A到G列
cell = worksheet.cell(row=row, column=col)
if cell.border is None or not cell.border:
cell.border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)
# 确保自动换行
if cell.alignment:
cell.alignment = Alignment(
horizontal=cell.alignment.horizontal,
vertical=cell.alignment.vertical,
wrap_text=True
)
else:
cell.alignment = Alignment(horizontal='center', vertical='center', wrap_text=True)
def calculate_row_height(self, row_data):
"""计算行高"""
# D列内容
d_content = str(row_data[3] or "")
d_char_count = self.calculate_char_count(d_content)
# B列内容
b_content = str(row_data[1] or "")
b_char_count = self.calculate_char_count(b_content)
# C列内容
c_content = str(row_data[2] or "")
c_char_count = self.calculate_char_count(c_content)
# 计算D列行高
if d_char_count <= 15:
d_height = 25
elif d_char_count <= 30:
d_height = 40
elif d_char_count <= 45:
d_height = 55
else:
levels = math.ceil(d_char_count / 15)
d_height = 15 * levels + 10
# 计算B列行高
if b_char_count <= 12:
b_height = 25
elif b_char_count <= 17:
b_height = 40
elif b_char_count <= 22:
b_height = 55
else:
levels = math.ceil(b_char_count / 6)
b_height = 10 * levels + 15
# 计算C列行高
if c_char_count <= 4:
c_height = 25
else:
levels = math.ceil(c_char_count / 4)
c_height = 10 * levels + 10
# 取最大值,至少25磅
return max(d_height, b_height, c_height, 25)
def calculate_char_count(self, text):
"""计算有效字符数"""
if not text:
return 0
char_count = 0
for char in str(text):
if char.isascii() and char.isalnum():
char_count += 0.5
elif char in '.,;:!?()[]{}"\'-':
char_count += 1 # 标点符号占一个字的位置
else:
char_count += 1
return char_count
def calculate_wc(self, yhj, HGhj):
"""计算wc值:(yhj-HGhj)/25的结果四舍五入取整数"""
wc = max(0, round((yhj - HGhj) / 25))
return int(wc)
def get_page_height(self, worksheet):
"""获取工作表的高度(实际使用的行数)"""
# 从最后一行向上查找,找到第一个有内容的行
for row in range(worksheet.max_row, 0, -1):
for col in range(1, worksheet.max_column + 1):
cell = worksheet.cell(row=row, column=col)
if cell.value is not None:
return row
return 0
def perform_end_operation(self, worksheet, current_row, wc, ym, jh):
"""结尾操作"""
# 保留当前写入行向下wc行的内容和格式,清除wc行以下的内容和格式
# 计算保留的范围:当前行 + 1 到 current_row + wc
# 计算需要清除的起始行:current_row + wc + 1
delete_start_row = current_row + wc + 1
# 保留的wc行设置行高25并添加细边框
for row in range(current_row + 1, delete_start_row):
worksheet.row_dimensions[row].height = 25
for col in range(1, 8): # A到G列
cell = worksheet.cell(row=row, column=col)
cell.border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)
# 清除delete_start_row及以下的行内容和格式
for row in range(delete_start_row, worksheet.max_row + 1):
for col in range(1, worksheet.max_column + 1):
cell = worksheet.cell(row=row, column=col)
# 清除值和格式
cell.value = None
cell.font = Font()
cell.alignment = Alignment()
cell.border = Border()
cell.fill = PatternFill()
# 写入页码(在写入行的向下wc+1行的F列)
page_row = current_row + wc + 1
page_cell = worksheet.cell(row=page_row, column=6)
page_cell.value = f"第{ym}页"
page_cell.font = Font(name='宋体', size=12)
page_cell.alignment = Alignment(horizontal='center')
worksheet.row_dimensions[page_row].height = self.ymhg
# 替换文中的页码和卷号占位符
for row in worksheet.iter_rows():
for cell in row:
if cell.value and isinstance(cell.value, str):
new_value = str(cell.value)
# 替换页码占位符
if 'yh' in new_value.lower():
new_value = new_value.replace('yh', str(ym))
new_value = new_value.replace('YH', str(ym))
new_value = new_value.replace('Yh', str(ym))
# 替换 "第 - 卷" 为 "第 jh 卷"
if '第 - 卷' in new_value:
new_value = new_value.replace('第 - 卷', f'第 {jh} 卷')
# 替换 "共 1 卷" 为 "共 jh 卷"
if '共 1 卷' in new_value:
new_value = new_value.replace('共 1 卷', f'共 {jh} 卷')
if new_value != str(cell.value):
cell.value = new_value
def perform_volume_break(self, worksheet, current_row, wc, ym, jh):
"""分卷操作"""
# 保留当前写入行向下wc行的内容和格式,清除wc行以下的内容和格式
# 计算保留的范围:当前行 + 1 到 current_row + wc
# 计算需要清除的起始行:current_row + wc + 1
delete_start_row = current_row + wc + 1
# 保留的wc行设置行高25并添加细边框
for row in range(current_row + 1, delete_start_row):
worksheet.row_dimensions[row].height = 25
for col in range(1, 8): # A到G列
cell = worksheet.cell(row=row, column=col)
cell.border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)
# 清除delete_start_row及以下的行内容和格式
for row in range(delete_start_row, worksheet.max_row + 1):
for col in range(1, worksheet.max_column + 1):
cell = worksheet.cell(row=row, column=col)
# 清除值和格式
cell.value = None
cell.font = Font()
cell.alignment = Alignment()
cell.border = Border()
cell.fill = PatternFill()
# 为保留的行添加细边框
for row in range(current_row + 1, delete_start_row):
for col in range(1, worksheet.max_column + 1):
cell = worksheet.cell(row=row, column=col)
cell.border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)
# 写入页码
page_row = current_row + wc + 1
page_cell = worksheet.cell(row=page_row, column=6)
page_cell.value = f"第{ym}页"
page_cell.font = Font(name='宋体', size=12)
page_cell.alignment = Alignment(horizontal='center')
worksheet.row_dimensions[page_row].height = self.ymhg # 修复:使用实例变量
# 替换文中的yh占位符为当前页码
for row in worksheet.iter_rows():
for cell in row:
if cell.value and isinstance(cell.value, str):
new_value = str(cell.value)
if 'yh' in new_value.lower():
new_value = new_value.replace('yh', str(ym))
new_value = new_value.replace('YH', str(ym))
new_value = new_value.replace('Yh', str(ym))
if new_value != str(cell.value):
cell.value = new_value
# 替换文中的ym占位符为当前页码
for row in worksheet.iter_rows():
for cell in row:
if cell.value and isinstance(cell.value, str):
new_value = str(cell.value)
# 替换ym(页码)
if 'ym' in new_value.lower():
new_value = new_value.replace('ym', str(ym))
new_value = new_value.replace('YM', str(ym))
new_value = new_value.replace('Ym', str(ym))
# 替换 "第 - 卷" 为 "第 jh 卷"
if '第 - 卷' in new_value:
new_value = new_value.replace('第 - 卷', f'第 {jh} 卷')
# 替换 "共 1 卷" 为 "共 jh 卷"
if new_value != str(cell.value):
cell.value = new_value
def perform_page_break(self, worksheet, current_row, wc, ym, jh):
"""分页操作"""
# 保留当前写入行向下wc行的内容和格式,清除wc行以下的内容和格式
# 计算保留的范围:当前行 + 1 到 current_row + wc
# 计算需要清除的起始行:current_row + wc + 1
delete_start_row = current_row + wc + 1
# 保留的wc行设置行高25并添加细边框
for row in range(current_row + 1, delete_start_row):
worksheet.row_dimensions[row].height = 25
for col in range(1, 8): # A到G列
cell = worksheet.cell(row=row, column=col)
cell.border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)
# 清除delete_start_row及以下的行内容和格式
for row in range(delete_start_row, worksheet.max_row + 1):
for col in range(1, worksheet.max_column + 1):
cell = worksheet.cell(row=row, column=col)
# 清除值和格式
cell.value = None
cell.font = Font()
cell.alignment = Alignment()
cell.border = Border()
cell.fill = PatternFill()
# 为保留的行添加细边框
for row in range(current_row + 1, delete_start_row):
for col in range(1, worksheet.max_column + 1):
cell = worksheet.cell(row=row, column=col)
cell.border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)
# 写入页码
page_row = current_row + wc + 1
page_cell = worksheet.cell(row=page_row, column=6)
page_cell.value = f"第{ym}页"
page_cell.font = Font(name='宋体', size=12)
page_cell.alignment = Alignment(horizontal='center')
worksheet.row_dimensions[page_row].height = self.ymhg # 修复:使用实例变量
# 替换卷号(在当前页中)
self.replace_volume_numbers_in_worksheet(worksheet, jh)
# 替换文中的yh占位符为当前页码
for row in worksheet.iter_rows():
for cell in row:
if cell.value and isinstance(cell.value, str):
new_value = str(cell.value)
if 'yh' in new_value.lower():
new_value = new_value.replace('yh', str(ym))
new_value = new_value.replace('YH', str(ym))
new_value = new_value.replace('Yh', str(ym))
if new_value != str(cell.value):
cell.value = new_value
# 替换 "第 - 卷" 为 "第 jh 卷"
if '第 - 卷' in new_value:
new_value = new_value.replace('第 - 卷', f'第 {jh} 卷')
if new_value != str(cell.value):
cell.value = new_value
def copy_page_to_result(self, source_ws, target_ws, start_row):
"""将复制页复制到结果文件"""
# 计算源工作表的行数
source_max_row = source_ws.max_row
source_max_col = source_ws.max_column
# 复制每一行
for row in range(1, source_max_row + 1):
for col in range(1, source_max_col + 1):
source_cell = source_ws.cell(row=row, column=col)
target_cell = target_ws.cell(row=start_row + row - 1, column=col)
# 复制值和样式
target_cell.value = source_cell.value
self.safe_copy_style(source_cell, target_cell)
# 复制合并单元格(需要调整行号)
for merged_range in source_ws.merged_cells.ranges:
# 解析合并范围
min_row, min_col, max_row, max_col = merged_range.min_row, merged_range.min_col, merged_range.max_row, merged_range.max_col
# 调整行号
new_min_row = min_row + start_row - 1
new_max_row = max_row + start_row - 1
# 合并单元格
target_ws.merge_cells(start_row=new_min_row, start_column=min_col,
end_row=new_max_row, end_column=max_col)
# 复制列宽
for col in range(1, source_ws.max_column + 1):
col_letter = get_column_letter(col)
if col_letter in source_ws.column_dimensions:
source_col_dim = source_ws.column_dimensions[col_letter]
if source_col_dim.width is not None:
target_ws.column_dimensions[col_letter].width = source_col_dim.width
# 复制行高(调整行号)
for row in range(1, source_ws.max_row + 1):
if row in source_ws.row_dimensions:
source_row_dim = source_ws.row_dimensions[row]
if source_row_dim.height is not None:
target_row = start_row + row - 1
target_ws.row_dimensions[target_row].height = source_row_dim.height
# 复制分页符(调整行号)
try:
for break_info in source_ws.row_breaks:
# 检查 break_info 的类型
if isinstance(break_info, tuple):
# 如果是元组,假设第一个元素是行号
target_row = break_info[0] + start_row - 1
elif hasattr(break_info, 'id'):
# 如果是对象,使用 id 属性
target_row = break_info.id + start_row - 1
else:
# 其他情况,跳过
continue
target_ws.row_breaks.append(Break(id=target_row))
except Exception as e:
# 捕获分页符复制错误,不影响整体处理
pass
def replace_volume_numbers(self, worksheet, jh):
"""替换卷号(在整个工作表中)"""
for row in worksheet.iter_rows():
for cell in row:
if cell.value and isinstance(cell.value, str):
# 替换jh和zjh(不区分大小写)
new_value = str(cell.value)
new_value = re.sub(r'\bjh\b', str(jh), new_value, flags=re.IGNORECASE)
new_value = re.sub(r'\bzjh\b', str(jh), new_value, flags=re.IGNORECASE)
if new_value != str(cell.value):
cell.value = new_value
def replace_volume_numbers_in_worksheet(self, worksheet, current_jh):
"""替换卷号(在单个工作表中)"""
for row in worksheet.iter_rows():
for cell in row:
if cell.value and isinstance(cell.value, str):
# 替换jh(当前卷号,不区分大小写)
new_value = str(cell.value)
new_value = re.sub(r'\bjh\b', str(current_jh), new_value, flags=re.IGNORECASE)
if new_value != str(cell.value):
cell.value = new_value
def add_page_breaks_to_result_file(self, workbook):
"""
在结果文件中对所有"第几页"单元格下方添加分页符(除了最后一个)
"""
worksheet = workbook.active
# 找到所有包含"第几页"的单元格位置
page_cells = []
for row in worksheet.iter_rows():
for cell in row:
if cell.value and isinstance(cell.value, str) and "第" in cell.value and "页" in cell.value:
# 检查是否是"第X页"格式
match = re.search(r'第(\d+)页', cell.value)
if match:
page_cells.append((cell.row, cell.column))
if not page_cells:
return # 没有找到页面标记
# 对除了最后一个页面标记的所有页面标记下方添加分页符
for page_row, page_col in page_cells[:-1]: # 排除最后一个
# 在页面标记的下一行添加分页符(我调整+1为+0)
next_row = page_row + 0
if next_row <= worksheet.max_row: # 确保不超过工作表边界
worksheet.row_breaks.append(Break(id=next_row))
def save_output_file(self, work_dir, folder_name, project_name, source_file, result_wb):
"""保存输出文件"""
# 清理文件夹名称
clean_folder_name = re.sub(r'[<>:"/\\|?*]', '_', folder_name)
clean_folder_name = clean_folder_name.strip()
# 创建输出文件夹
output_dir = os.path.join(work_dir, clean_folder_name)
counter = 1
original_dir = output_dir
while os.path.exists(output_dir):
output_dir = f"{original_dir}_{counter}"
counter += 1
os.makedirs(output_dir, exist_ok=True)
# 生成文件名:待复制文件名称(模板C2内容)
source_name = os.path.splitext(os.path.basename(source_file))[0]
result_name = f"{source_name}({project_name}).xlsx"
result_path = os.path.join(output_dir, result_name)
# 处理文件名冲突
counter = 1
base_name, ext = os.path.splitext(result_name)
while os.path.exists(result_path):
result_path = os.path.join(output_dir, f"{base_name}_{counter}{ext}")
counter += 1
# 在保存前添加分页符到结果文件
self.add_page_breaks_to_result_file(result_wb)
# 设置打印区域
result_ws = result_wb.active
result_ws.print_area = 'A1:G{}'.format(result_ws.max_row)
# 保存文件
result_wb.save(result_path)
self.log_message(f"文件已保存到: {result_path}", "success")
return result_path
def main():
"""主函数"""
root = tk.Tk()
# 设置窗口居中
window_width = 1000
window_height = 800
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
center_x = int(screen_width / 2 - window_width / 2)
center_y = int(screen_height / 2 - window_height / 2)
root.geometry(f'{window_width}x{window_height}+{center_x}+{center_y}')
# 设置最小尺寸
root.minsize(800, 600)
# 创建应用
app = ProfessionalDirectoryGenerator(root)
# 运行主循环
root.mainloop()
if __name__ == "__main__":
main()