feat: enhance progress reporting granularity in data extraction workflow

- Break down query phase into 3 detailed steps (read file, query database, complete)
- Split download batch process into 7 granular steps per batch (clear, fill, search, wait, select, configure, download)
- Divide conversion phase into 2 steps per file plus 3 final steps (merge, cleanup)
- Add 'action' parameter to all progress reports for better categorization
- Improve user feedback with more detailed status messages and completion indicators

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Misaka_Company
2026-02-05 15:17:18 +08:00
parent 2ca53f6a55
commit 3180ccacb8

View File

@@ -84,10 +84,29 @@ class DiscreteMaterialPlanExtractor:
Returns:
生产订单号列表
"""
if report_progress:
self._report_progress(
"query",
1,
3,
"正在读取总排号文件...",
action="read_file",
)
# 读取总排号
production_ids = read_production_ids(production_id_file)
self._print(f"从文件读取到 {len(production_ids)} 个总排号")
if report_progress:
self._report_progress(
"query",
2,
3,
f"正在查询数据库({len(production_ids)} 个总排号)...",
action="query_database",
production_id_count=len(production_ids),
)
# 查询数据库获取生产订单号
order_ids = query_production_order_numbers(production_ids)
self._print(f"查询到 {len(order_ids)} 个生产订单号")
@@ -95,10 +114,11 @@ class DiscreteMaterialPlanExtractor:
if report_progress:
self._report_progress(
"query",
1,
1,
f"查询到 {len(order_ids)} 个生产订单号",
count=len(order_ids),
3,
3,
f"查询完成:获取{len(order_ids)} 个生产订单号",
action="query_complete",
order_id_count=len(order_ids),
)
return order_ids
@@ -123,18 +143,51 @@ class DiscreteMaterialPlanExtractor:
import re
import time
# 清空文本框
# 步骤1清空文本框
self._report_progress(
"download",
batch_index * 7 + 1,
total_batches * 7,
f"{batch_index + 1}/{total_batches} 批 - 准备输入订单号",
batch_index=batch_index + 1,
action="clear_textbox",
)
textbox = inner_frame.get_by_role("textbox", name="来源生产订单号")
textbox.fill("")
# 填充订单号
# 步骤2填充订单号
self._report_progress(
"download",
batch_index * 7 + 2,
total_batches * 7,
f"{batch_index + 1}/{total_batches} 批 - 输入 {len(order_ids)} 个订单号",
batch_index=batch_index + 1,
action="fill_order_ids",
order_count=len(order_ids),
)
textbox.fill(",".join(order_ids))
# 点击查询
# 步骤3点击查询
self._report_progress(
"download",
batch_index * 7 + 3,
total_batches * 7,
f"{batch_index + 1}/{total_batches} 批 - 提交查询请求",
batch_index=batch_index + 1,
action="click_search",
)
inner_frame.locator(".search-component-searchBtn").click()
self._print(f"{batch_index + 1} 批查询完成,等待加载结果...")
# 等待加载完成
# 步骤4等待加载完成
self._report_progress(
"download",
batch_index * 7 + 4,
total_batches * 7,
f"{batch_index + 1}/{total_batches} 批 - 等待数据加载...",
batch_index=batch_index + 1,
action="wait_loading",
)
loading_locator = inner_frame.locator("div").filter(has_text="加载中").nth(1)
try:
loading_locator.wait_for(state="visible", timeout=3000)
@@ -149,9 +202,27 @@ class DiscreteMaterialPlanExtractor:
self._print(f"=== 调试暂停:第 {batch_index + 1} 批 ===")
page1.pause()
# 选择所有数据
# 步骤5选择所有数据
self._report_progress(
"download",
batch_index * 7 + 5,
total_batches * 7,
f"{batch_index + 1}/{total_batches} 批 - 选择所有数据行",
batch_index=batch_index + 1,
action="select_all_rows",
)
inner_frame.get_by_role("row", name="序号").get_by_label("").click()
# 步骤6配置并触发导出
self._report_progress(
"download",
batch_index * 7 + 6,
total_batches * 7,
f"{batch_index + 1}/{total_batches} 批 - 配置导出参数",
batch_index=batch_index + 1,
action="configure_export",
)
# 点击输出
inner_frame.get_by_role("button", name="更多").hover()
inner_frame.get_by_text("输出", exact=True).click()
@@ -164,7 +235,15 @@ class DiscreteMaterialPlanExtractor:
)
input_box.fill("300000")
# 下载文件
# 步骤7下载文件
self._report_progress(
"download",
batch_index * 7 + 7,
total_batches * 7,
f"{batch_index + 1}/{total_batches} 批 - 正在下载文件...",
batch_index=batch_index + 1,
action="downloading_file",
)
download_path = f"D:/python/playwrite/data/temp_batch_{batch_index + 1}.xlsx"
with page1.expect_download() as download_info:
inner_frame.get_by_role("button", name="确定(Y)").click()
@@ -173,21 +252,17 @@ class DiscreteMaterialPlanExtractor:
download.save_as(download_path)
self._print(f"{batch_index + 1} 批下载完成: {download_path}")
# 报告进度
# 报告批次完成
self._report_progress(
"download",
batch_index + 1,
total_batches,
f"{batch_index + 1}/{total_batches} 批下载完成",
(batch_index + 1) * 7,
total_batches * 7,
f"{batch_index + 1}/{total_batches} 批下载完成",
batch_index=batch_index + 1,
action="batch_complete",
file_path=download_path,
)
# 关闭输出对话框(如果有的话)
# try:
# inner_frame.get_by_role("button", name="取消").click()
# except:
# pass
# 等待页面恢复,准备下一次查询
time.sleep(1)
@@ -204,37 +279,77 @@ class DiscreteMaterialPlanExtractor:
self._print(f"输出目录: {output_dir}")
self._print(f"输出文件名: {output_filename}")
# 确保输出文件的父目录存在
# 步骤1检查并创建输出目录
self._report_progress(
"convert",
1,
len(file_paths) * 2 + 3,
"准备转换:检查输出目录",
action="check_directory",
)
if output_dir and not os.path.exists(output_dir):
self._print(f"创建输出目录: {output_dir}")
os.makedirs(output_dir)
all_dataframes = []
# 步骤2-N转换每个文件
for i, file_path in enumerate(file_paths, 1):
self._print(f"转换第 {i} 个文件: {file_path}")
# 报告进度
# 报告开始转换
self._report_progress(
"convert",
i,
len(file_paths),
f"转换第 {i}/{len(file_paths)} 个文件",
1 + (i - 1) * 2 + 1,
len(file_paths) * 2 + 3,
f"正在转换文件 {i}/{len(file_paths)}",
file_index=i,
file_path=file_path,
action="converting_file",
)
df = self.converter.convert(file_path, output_file=None) # 只转换,不保存
all_dataframes.append(df)
self._print(f" 提取到 {len(df)} 条记录")
# 报告转换完成
self._report_progress(
"convert",
1 + (i - 1) * 2 + 2,
len(file_paths) * 2 + 3,
f"文件 {i}/{len(file_paths)} 转换完成({len(df)} 条记录)",
file_index=i,
record_count=len(df),
action="file_converted",
)
if all_dataframes:
# 步骤N+1合并数据
self._report_progress(
"convert",
len(file_paths) * 2 + 2,
len(file_paths) * 2 + 3,
f"正在合并 {len(all_dataframes)} 个文件的数据...",
action="merging_data",
file_count=len(all_dataframes),
)
self._print(f"\n合并 {len(all_dataframes)} 个文件的数据...")
merged_df = pd.concat(all_dataframes, ignore_index=True)
merged_df.to_excel(output_path, index=False)
self._print(f"合并完成: {output_path}, 总共 {len(merged_df)} 条记录")
# 删除临时文件
# 步骤N+2删除临时文件
self._report_progress(
"convert",
len(file_paths) * 2 + 3,
len(file_paths) * 2 + 3,
f"清理临时文件...",
action="cleanup",
total_records=len(merged_df),
)
for file_path in file_paths:
os.remove(file_path)
self._print(f"已删除临时文件: {file_path}")
@@ -243,21 +358,23 @@ class DiscreteMaterialPlanExtractor:
return None
def setup_query_interface(self, inner_frame):
"""设置查询界面"""
"""设置查询界面(不报告进度,由 extract 统一报告)"""
import re
# 点击图标按钮打开查询界面
# 打开查询界面
inner_frame.locator(".search-name-wrapper > .iconfont").click()
inner_frame.get_by_text("订单号查询").click()
# 选择"全部"标签
inner_frame.get_by_role("tab", name="全部").click()
# 填充并验证,如果失败则重试
# 填充并验证
max_retries = 3
expected_value = "5000"
for attempt in range(max_retries):
inner_frame.locator("#rc_select_0").fill(expected_value)
inner_frame.locator("#rc_select_0").press("Enter")
# 检查填充是否成功
actual_value = inner_frame.locator("#rc_select_0").input_value()
if actual_value == expected_value:
self._print(f"文本框填充成功: {expected_value}")
@@ -280,28 +397,21 @@ class DiscreteMaterialPlanExtractor:
debug_batch=None,
progress_callback=None,
):
"""
执行完整的数据提取流程
Args:
production_id_file: ProductionID.txt 文件路径
data_dir: 数据保存目录
output_file: 最终输出文件路径
debug_mode: 是否启用调试模式
debug_batch: 调试批次号
progress_callback: 进度回调函数(覆盖初始化时的回调)
Returns:
输出文件路径
"""
# 保存原始回调
"""执行完整的数据提取流程"""
original_callback = self.progress_callback
# 使用传入的回调或初始化时的回调
self.progress_callback = progress_callback or self.progress_callback
try:
with sync_playwright() as playwright:
# 调用登录模块
# 步骤1启动浏览器并登录
self._report_progress(
"login",
1,
3, # 保持 3 步
"启动浏览器并登录...",
action="launch_browser",
)
browser, context, page, main_frame = login(
playwright=playwright,
username=self.username,
@@ -310,41 +420,48 @@ class DiscreteMaterialPlanExtractor:
ignore_https_errors=True,
)
# 登录完成
self._report_progress("login", 1, 1, "登录成功")
# 步骤2打开功能页面
self._report_progress(
"login",
2,
3, # 保持 3 步
"登录成功,打开功能页面...",
action="open_function_page",
)
self._print("=" * 80)
self._print("开始执行离散备料计划维护数据提取")
self._print("=" * 80)
# 登录成功后可以进行后续操作
# 点击打开"功能菜单"
main_frame.locator("i").first.click()
# 点击打开"离散备料计划维护"
with page.expect_popup() as page1_info:
main_frame.get_by_title(
"离散备料计划维护", exact=True
).first.click()
page1 = page1_info.value
# 获取 nested iframe
main_frame = page1.locator("#forwardFrame").content_frame
inner_frame_locator = main_frame.locator("#mainiframe")
inner_frame_locator.wait_for(state="visible", timeout=15000)
inner_frame = inner_frame_locator.content_frame
# 设置查询界面
# 步骤3设置查询界面
self._report_progress(
"login",
3,
3, # 保持 3 步
"配置查询界面...",
action="setup_query_interface",
)
self.setup_query_interface(inner_frame)
# 读取总排号并查询生产订单号
# 后续代码保持不变...
order_ids = self.get_production_order_numbers(
production_id_file, report_progress=True
)
# 按批次下载
downloaded_files = []
# 计算总批次数
total_batches = sum(
1 for _ in self.group_order_ids(order_ids, self.batch_size)
)
@@ -356,16 +473,6 @@ class DiscreteMaterialPlanExtractor:
f"\n=== 开始处理第 {batch_index + 1} 批,共 {len(order_ids_batch)} 个订单号 ==="
)
# 报告开始下载批次
self._report_progress(
"download",
batch_index,
total_batches,
f"正在下载第 {batch_index + 1}/{total_batches} 批...",
batch_index=batch_index + 1,
batch_size=len(order_ids_batch),
)
downloaded_file = self.download_batch(
inner_frame,
order_ids_batch,
@@ -377,20 +484,25 @@ class DiscreteMaterialPlanExtractor:
)
downloaded_files.append(downloaded_file)
# 执行账号注销
self._print("\n开始执行账号注销...")
self._report_progress("logout", 1, 1, "正在注销账号...")
self._report_progress(
"logout",
1,
2,
"正在注销账号...",
action="logout_start",
)
logout(main_frame, verbose=self.verbose)
# 转换并合并文件
if downloaded_files:
self._report_progress(
"convert",
0,
len(downloaded_files),
f"开始转换并合并 {len(downloaded_files)} 个文件",
file_count=len(downloaded_files),
"logout",
2,
2,
"注销完成 ✓",
action="logout_complete",
)
if downloaded_files:
self._print(
f"\n=== 开始转换并合并 {len(downloaded_files)} 个文件 ==="
)
@@ -401,19 +513,18 @@ class DiscreteMaterialPlanExtractor:
self._print(f"\n=== 全部完成 ===")
self._print(f"最终文件: {output_file}")
# 报告完成
self._report_progress(
"complete", 1, 1, "提取完成", output_file=output_file
"complete", 1, 1, "数据提取完成",
output_file=output_file,
action="all_complete",
)
# 关闭浏览器
context.close()
browser.close()
return output_file
finally:
# 恢复原始回调
self.progress_callback = original_callback