代码和代码结构更新
This commit is contained in:
30
htmlProcess/htmlReportProcess_Merge_cmd/.gitignore
vendored
Normal file
30
htmlProcess/htmlReportProcess_Merge_cmd/.gitignore
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
/build/*
|
||||
/build
|
||||
/dist/*
|
||||
/dist
|
||||
/source/*
|
||||
/source
|
||||
|
||||
|
||||
|
||||
|
||||
htmlReportProcess_Merge_picHtml_V3.py
|
||||
|
||||
htmlReportProcess_Merge_picHtml_V2.py
|
||||
|
||||
htmlReportProcess_Merge_pic_V2.py
|
||||
|
||||
#/htmlReportProcess*/
|
||||
|
||||
|
||||
htmlReportProcess_cmd_pV2.py
|
||||
htmlReportProcess_cmd_pV3.py
|
||||
|
||||
|
||||
htmlReportProcess_cmd_V2.py
|
||||
|
||||
htmlReportProcess.py
|
||||
|
||||
|
||||
htmlReportProcess_Merge_cmd_V2.py
|
||||
htmlReportProcess_Merge.py
|
||||
@@ -0,0 +1,434 @@
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
import time
|
||||
import tkinter as tk
|
||||
from tkinter import filedialog
|
||||
from openpyxl import Workbook, load_workbook
|
||||
from datetime import datetime
|
||||
|
||||
from colorama import Fore, Style, init
|
||||
|
||||
class TestReportMerger:
|
||||
def __init__(self):
|
||||
self.source_files = []
|
||||
self.merged_data = []
|
||||
self.output_filepath = ""
|
||||
self.selected_folder = ""
|
||||
# 进度统计
|
||||
self.stats = {
|
||||
"total_files": 0,
|
||||
"processed_files": 0,
|
||||
"skipped_no_sheet": 0,
|
||||
"errors": 0,
|
||||
"total_rows_merged": 0
|
||||
}
|
||||
|
||||
def _print_stage(self, msg):
|
||||
print(f"\n=== {msg} ===")
|
||||
|
||||
def _print_progress(self, current, total, prefix="处理进度"):
|
||||
percent = (current / total * 100) if total else 0
|
||||
bar_len = 30
|
||||
filled = int(bar_len * current / total) if total else 0
|
||||
bar = "█" * filled + "-" * (bar_len - filled)
|
||||
print(f"\r{prefix}: [{bar}] {current}/{total} ({percent:.1f}%)", end="", flush=True)
|
||||
|
||||
def select_directory(self):
|
||||
"""选择包含测试报告的目录"""
|
||||
root = tk.Tk()
|
||||
root.withdraw() # 隐藏主窗口
|
||||
self.selected_folder = filedialog.askdirectory(title="选择包含测试报告的目录")
|
||||
return self.selected_folder
|
||||
|
||||
def _get_directory_from_console(self):
|
||||
"""从控制台获取目录路径"""
|
||||
while True:
|
||||
print(f"\n{Fore.CYAN}=== HTML-excel报告处理程序 ===")
|
||||
print(f"{Fore.WHITE}请输入包含测试报告文件的目录路径:")
|
||||
self.selected_folder = input("> ").strip()
|
||||
|
||||
if not self.selected_folder:
|
||||
print(f"{Fore.YELLOW}⚠ 路径不能为空,请重新输入")
|
||||
continue
|
||||
|
||||
# 处理路径中的引号
|
||||
self.selected_folder = self.selected_folder.strip('"\'')
|
||||
|
||||
if not os.path.exists(self.selected_folder):
|
||||
print(f"{Fore.RED}❌ 路径不存在,请重新输入")
|
||||
continue
|
||||
|
||||
if not os.path.isdir(self.selected_folder):
|
||||
print(f"{Fore.RED}❌ 请输入目录路径,而不是文件路径")
|
||||
continue
|
||||
|
||||
return self.selected_folder
|
||||
|
||||
|
||||
def scan_files(self):
|
||||
"""扫描目录下的Excel文件(仅处理 .xlsx,忽略临时文件)"""
|
||||
if not self.selected_folder:
|
||||
return False
|
||||
|
||||
self._print_stage("扫描文件")
|
||||
# 仅处理 .xlsx,忽略 ~$ 开头的临时文件
|
||||
files = [
|
||||
os.path.join(self.selected_folder, f)
|
||||
for f in os.listdir(self.selected_folder)
|
||||
if f.lower().endswith(".xlsx") and not f.startswith("~$")
|
||||
]
|
||||
|
||||
self.source_files = files
|
||||
self.stats["total_files"] = len(self.source_files)
|
||||
|
||||
if self.stats["total_files"] == 0:
|
||||
print("指定目录中没有找到可处理的 .xlsx 文件")
|
||||
return False
|
||||
|
||||
print(f"找到 {self.stats['total_files']} 个 .xlsx 文件")
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def _parse_source_filename(value):
|
||||
"""
|
||||
解析 Source File name 字段,提取 SN、TestCycleTime、Cell。
|
||||
支持形如:
|
||||
Fxxxx(...日期时间...)-CELL.html
|
||||
例如:F27140001X3M00004013683JK00190(2025-09-22 09-03-22)-14.html
|
||||
"""
|
||||
sn, ts, cell = "", "", ""
|
||||
if not value:
|
||||
return sn, ts, cell
|
||||
|
||||
# 只取基名,防止包含路径
|
||||
base = os.path.basename(str(value)).strip()
|
||||
|
||||
# 主模式:SN(时间)-Cell[.扩展名]
|
||||
m = re.match(r'^(?P<sn>[^()\-]+)\((?P<time>[^)]+)\)-(?P<cell>\d+)', base)
|
||||
if m:
|
||||
sn = m.group('sn').strip()
|
||||
ts = m.group('time').strip()
|
||||
cell = m.group('cell').strip()
|
||||
return sn, ts, cell
|
||||
|
||||
# 兜底:尝试匹配 SN(以 F 开头的字母数字串)、时间(括号内)、Cell(-数字)
|
||||
sn_match = re.search(r'\bF[A-Z0-9]+\b', base)
|
||||
time_match = re.search(r'\(([^)]+)\)', base)
|
||||
cell_match = re.search(r'-(\d+)(?:\.\w+)?$', base)
|
||||
|
||||
if sn_match:
|
||||
sn = sn_match.group(0).strip()
|
||||
if time_match:
|
||||
ts = time_match.group(1).strip()
|
||||
if cell_match:
|
||||
cell = cell_match.group(1).strip()
|
||||
|
||||
return sn, ts, cell
|
||||
|
||||
def merge_reports(self):
|
||||
"""合并所有报告中的 'All Tests' 工作表,并拆分 Source File name 为 SN/TestCycleTime/Cell 列"""
|
||||
if not self.source_files:
|
||||
return False
|
||||
|
||||
self._print_stage("合并报告数据")
|
||||
start_time = time.time()
|
||||
|
||||
# 初始化合并数据,保留表头
|
||||
self.merged_data = []
|
||||
header_added = False
|
||||
source_col_idx = None # 记录“Source File name”列索引
|
||||
|
||||
total_files = self.stats["total_files"]
|
||||
for idx, file_path in enumerate(self.source_files, start=1):
|
||||
filename = os.path.basename(file_path)
|
||||
# 文件级进度条
|
||||
self._print_progress(idx, total_files, prefix="文件处理")
|
||||
try:
|
||||
wb = load_workbook(file_path, read_only=True, data_only=True)
|
||||
if 'All Tests' not in wb.sheetnames:
|
||||
self.stats["skipped_no_sheet"] += 1
|
||||
print(f"\n文件 {filename} 中没有 'All Tests' 工作表,已跳过")
|
||||
wb.close()
|
||||
continue
|
||||
|
||||
sheet = wb['All Tests']
|
||||
|
||||
# 添加表头(只添加一次)
|
||||
if not header_added and sheet.max_row > 0:
|
||||
header = [cell.value for cell in sheet[1]]
|
||||
# 定位 Source File name 列(大小写不敏感)
|
||||
source_col_idx = None
|
||||
for i, h in enumerate(header):
|
||||
if h and str(h).strip().lower() == "source file name":
|
||||
source_col_idx = i
|
||||
break
|
||||
|
||||
# 扩展表头:新增 SN / TestCycleTime / Cell / 数据来源
|
||||
extended_header = list(header)
|
||||
extended_header += ["SN", "TestCycleTime", "Cell", "数据来源"]
|
||||
self.merged_data.append(extended_header)
|
||||
header_added = True
|
||||
|
||||
# 统计行数(不含表头)
|
||||
data_rows_count = max(sheet.max_row - 1, 0)
|
||||
|
||||
# 添加数据行
|
||||
added_rows = 0
|
||||
for row in sheet.iter_rows(min_row=2, values_only=True):
|
||||
if row is None:
|
||||
continue
|
||||
# 过滤全空行
|
||||
if all(cell is None for cell in row):
|
||||
continue
|
||||
|
||||
row_list = list(row)
|
||||
|
||||
# 从 Source File name 列解析三项
|
||||
sn, ts, cell = "", "", ""
|
||||
if source_col_idx is not None and source_col_idx < len(row_list):
|
||||
sn, ts, cell = self._parse_source_filename(row_list[source_col_idx])
|
||||
|
||||
# 追加解析列与数据来源列
|
||||
row_list += [sn, ts, cell, filename]
|
||||
self.merged_data.append(row_list)
|
||||
added_rows += 1
|
||||
|
||||
wb.close()
|
||||
|
||||
self.stats["processed_files"] += 1
|
||||
self.stats["total_rows_merged"] += added_rows
|
||||
# 每个文件处理完成后给出简报
|
||||
print(f"\n→ 已处理: {filename} | 预估行数: {data_rows_count} | 实际合并行数: {added_rows} | 累计合并行数: {self.stats['total_rows_merged']}")
|
||||
|
||||
except Exception as e:
|
||||
self.stats["errors"] += 1
|
||||
print(f"\n处理文件 {filename} 时出错: {type(e).__name__}: {str(e)}")
|
||||
continue
|
||||
|
||||
elapsed = time.time() - start_time
|
||||
print(f"\n合并阶段完成,耗时: {elapsed:.1f} 秒")
|
||||
return len(self.merged_data) > 1 # 至少有一个表头和一个数据行
|
||||
|
||||
def save_merged_report(self):
|
||||
"""保存合并后的报告到选择的目录"""
|
||||
if not self.merged_data or not self.selected_folder:
|
||||
return False
|
||||
|
||||
self._print_stage("保存合并结果")
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
output_filename = f"测试报告合并_{timestamp}.xlsx"
|
||||
self.output_filepath = os.path.join(self.selected_folder, output_filename)
|
||||
|
||||
try:
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
ws.title = "Merged All Tests"
|
||||
|
||||
# 写入时也给出简单的进度(每写入一定行数提示一次)
|
||||
total_rows = len(self.merged_data)
|
||||
last_print = time.time()
|
||||
for i, row in enumerate(self.merged_data, start=1):
|
||||
ws.append(row)
|
||||
# 控制输出频率,避免大量打印影响速度
|
||||
if i == total_rows or (time.time() - last_print) > 0.5:
|
||||
self._print_progress(i, total_rows, prefix="写入Excel行")
|
||||
last_print = time.time()
|
||||
|
||||
wb.save(self.output_filepath)
|
||||
print(f"\n文件已保存: {self.output_filepath}")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"保存合并报告时出错: {type(e).__name__}: {str(e)}")
|
||||
return False
|
||||
|
||||
def save_merged_report_xlsxwriter(self):
|
||||
"""使用xlsxwriter引擎保存,带进度显示"""
|
||||
if not self.merged_data or not self.selected_folder:
|
||||
return False
|
||||
|
||||
self._print_stage("保存合并结果")
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
output_filename = f"测试报告合并_{timestamp}.xlsx"
|
||||
self.output_filepath = os.path.join(self.selected_folder, output_filename)
|
||||
|
||||
try:
|
||||
import pandas as pd
|
||||
import xlsxwriter
|
||||
|
||||
# 将数据转换为DataFrame
|
||||
headers = self.merged_data[0]
|
||||
data_rows = self.merged_data[1:]
|
||||
total_rows = len(data_rows)
|
||||
|
||||
print(f"开始保存,共{total_rows}行数据到工作表 'Merged All Tests'...")
|
||||
|
||||
# 创建workbook和worksheet
|
||||
workbook = xlsxwriter.Workbook(self.output_filepath)
|
||||
worksheet = workbook.add_worksheet('Merged All Tests')
|
||||
|
||||
# 写入表头
|
||||
header_format = workbook.add_format({
|
||||
'bold': True,
|
||||
'fg_color': '#D7E4BC',
|
||||
'border': 1
|
||||
})
|
||||
|
||||
for col_num, header in enumerate(headers):
|
||||
worksheet.write(0, col_num, header, header_format)
|
||||
|
||||
# 写入数据并显示进度
|
||||
processed_rows = 0
|
||||
batch_size = 1000 # 每批处理的行数
|
||||
|
||||
for start_idx in range(0, total_rows, batch_size):
|
||||
end_idx = min(start_idx + batch_size, total_rows)
|
||||
batch_data = data_rows[start_idx:end_idx]
|
||||
|
||||
# 写入这一批数据
|
||||
for row_offset, row_data in enumerate(batch_data):
|
||||
for col_num, cell_value in enumerate(row_data):
|
||||
worksheet.write(start_idx + row_offset + 1, col_num, cell_value)
|
||||
|
||||
processed_rows += 1
|
||||
|
||||
# 每处理一定数量或最后一行时更新进度
|
||||
if processed_rows % max(1, total_rows // 20) == 0 or processed_rows == total_rows:
|
||||
percentage = int((processed_rows / total_rows) * 100)
|
||||
print(f"\r保存进度: {percentage}% ({processed_rows}/{total_rows}行)", end="", flush=True)
|
||||
|
||||
# 自动调整列宽
|
||||
for idx, _ in enumerate(headers):
|
||||
worksheet.set_column(idx, idx, 15) # 默认宽度
|
||||
|
||||
workbook.close()
|
||||
|
||||
print("\r保存完成!" + " " * 40) # 清空进度行
|
||||
print(f"文件已保存: {self.output_filepath}")
|
||||
print(f"工作表名: Merged All Tests")
|
||||
return True
|
||||
|
||||
except ImportError:
|
||||
print("xlsxwriter未安装,使用备选方案")
|
||||
return self.save_merged_report()
|
||||
|
||||
def save_merged_report_xlsxwriter_with_progress(self):
|
||||
"""使用xlsxwriter带进度显示的保存"""
|
||||
if not self.merged_data or not self.selected_folder:
|
||||
return False
|
||||
|
||||
self._print_stage("保存合并结果")
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
output_filename = f"测试报告合并_{timestamp}.xlsx"
|
||||
self.output_filepath = os.path.join(self.selected_folder, output_filename)
|
||||
|
||||
try:
|
||||
import xlsxwriter
|
||||
from tqdm import tqdm # 可选可选:更美观的进度条
|
||||
|
||||
headers = self.merged_data[0]
|
||||
data_rows = self.merged_data[1:]
|
||||
total_rows = len(data_rows)
|
||||
|
||||
print(f"开始保存,共{len(headers)}列{total_rows}行数据...")
|
||||
|
||||
# 创建workbook和worksheet
|
||||
workbook = xlsxwriter.Workbook(self.output_filepath)
|
||||
# worksheet = workbook.add_worksheet()
|
||||
worksheet = workbook.add_worksheet('Merged All Tests')
|
||||
# 写入表头
|
||||
for col_num, header in enumerate(headers):
|
||||
worksheet.write(0, col_num, header)
|
||||
|
||||
# 批量写入数据并显示进度
|
||||
batch_size = 500 # 每批处理的行数
|
||||
|
||||
# 如果有tqdm就用美观进度条,否则用简易版本
|
||||
try:
|
||||
from tqdm import tqdm
|
||||
pbar = tqdm(total=total_rows, desc="保存进度", unit="行")
|
||||
except ImportError:
|
||||
pbar = None
|
||||
|
||||
rows_saved = 0
|
||||
for start_idx in range(0, total_rows, batch_size):
|
||||
end_idx = min(start_idx + batch_size, total_rows)
|
||||
batch_data = data_rows[start_idx:end_idx]
|
||||
|
||||
# 写入这一批数据
|
||||
for row_offset, row_data in enumerate(batch_data):
|
||||
for col_num, cell_value in enumerate(row_data):
|
||||
worksheet.write(start_idx + row_offset + 1, col_num, cell_value)
|
||||
|
||||
rows_saved += 1
|
||||
if pbar:
|
||||
pbar.update(1)
|
||||
elif rows_saved % max(1, total_rows // 10) == 0 or rows_saved == total_rows:
|
||||
percentage = int((rows_saved / total_rows) * 100)
|
||||
print(f"\r保存进度: {percentage}% ({rows_saved}/{total_rows}行)", end="", flush=True)
|
||||
|
||||
if pbar:
|
||||
pbar.close()
|
||||
else:
|
||||
print("\r保存完成!" + " " * 30) # 清空进度行
|
||||
|
||||
workbook.close()
|
||||
print(f"文件已保存: {self.output_filepath}")
|
||||
return True
|
||||
|
||||
except ImportError:
|
||||
print("xlsxwriter, tqdm 未安装,使用备选方案")
|
||||
return self.save_merged_report_xlsxwriter()
|
||||
|
||||
def run(self):
|
||||
"""运行合并流程"""
|
||||
print("=== 测试报告合并工具 ===")
|
||||
|
||||
# 1. 选择目录
|
||||
# if not self.select_directory():
|
||||
# print("未选择目录,程序退出")
|
||||
# return
|
||||
source_dir = self._get_directory_from_console()
|
||||
if not source_dir:
|
||||
print(f"{Fore.RED}❌ 未选择目录,程序退出")
|
||||
return
|
||||
|
||||
# 2. 扫描文件
|
||||
if not self.scan_files():
|
||||
print("指定目录中没有找到Excel文件")
|
||||
return
|
||||
|
||||
print(f"准备处理 {len(self.source_files)} 个文件...")
|
||||
|
||||
# 3. 合并报告
|
||||
if not self.merge_reports():
|
||||
print("没有找到包含 'All Tests' 工作表的文件或合并数据为空")
|
||||
# 汇总统计
|
||||
self._print_stage("处理摘要")
|
||||
print(f"总文件数: {self.stats['total_files']}")
|
||||
print(f"成功处理: {self.stats['processed_files']}")
|
||||
print(f"跳过(无工作表): {self.stats['skipped_no_sheet']}")
|
||||
print(f"错误文件: {self.stats['errors']}")
|
||||
print(f"合并总行数: {self.stats['total_rows_merged']}")
|
||||
return
|
||||
|
||||
# 4. 保存结果
|
||||
# if self.save_merged_report():
|
||||
# if self.save_merged_report_xlsxwriter():
|
||||
if self.save_merged_report_xlsxwriter_with_progress():
|
||||
print("合并完成!")
|
||||
else:
|
||||
print("保存合并报告时出错")
|
||||
|
||||
# 汇总统计
|
||||
self._print_stage("处理摘要")
|
||||
print(f"总文件数: {self.stats['total_files']}")
|
||||
print(f"成功处理: {self.stats['processed_files']}")
|
||||
print(f"跳过(无工作表): {self.stats['skipped_no_sheet']}")
|
||||
print(f"错误文件: {self.stats['errors']}")
|
||||
print(f"合并总行数: {self.stats['total_rows_merged']}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
merger = TestReportMerger()
|
||||
merger.run()
|
||||
Reference in New Issue
Block a user