代码和代码结构更新

This commit is contained in:
2026-02-05 09:04:10 +08:00
parent 5c846eae94
commit 46ae47274d
9 changed files with 4742 additions and 1 deletions

View 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

View File

@@ -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()