fix(zto): 修复双月日期控件 real-today 定位失败(月初1号隐藏 ghost cell)

中通 jQuery Date Range Picker 始终显示双月面板。月初时(如8月1日),
当天日期同时出现在 month1(隐藏 ghost cell)和 month2(可见 cell),
两者的 CSS class 都含 real-today,.first 取到隐藏元素导致超时。

改为直接从 Python datetime 计算目标时间戳,新增 _zto_find_visible_day
查找可见日期格(跳过 display:none 的 ghost cell),同步修复
_zto_flip_to_target_month 的可见性判断。

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Misaka
2026-08-01 17:33:20 +08:00
parent eaf56c1c9e
commit b916d82058

View File

@@ -4,7 +4,7 @@ import os
import re import re
import time import time
import yaml import yaml
from datetime import datetime from datetime import datetime, timedelta
import pandas as pd import pandas as pd
from inbound_verify.paths import DOWNLOAD_DIR, CONFIG_PATH from inbound_verify.paths import DOWNLOAD_DIR, CONFIG_PATH
@@ -93,20 +93,50 @@ def _dom_click(locator):
) )
def _zto_compute_target_time(offset):
"""直接从 Python datetime 计算目标日期的毫秒级时间戳(本地时区零点)。
不再从 DOM 的 real-today 元素读取 time 属性,避免双月视图下 real-today
同时出现在 month1隐藏 ghost cell和 month2可见导致 .first 取到隐藏元素。
"""
target_date = datetime.now().date() - timedelta(days=offset)
target_dt = datetime(target_date.year, target_date.month, target_date.day)
return int(target_dt.timestamp() * 1000)
def _zto_find_visible_day(frame_locator, target_time):
"""在双月日期控件中查找可见的日期格子。
jQuery Date Range Picker 双月视图下,同一天可能出现在两个面板中:
- month1左面板的溢出 ghost celldisplay:none不可见
- month2右面板的正常 cell可见
同一日期在 DOM 中可能有毫秒级差异(零点 vs 23:59:59遍历匹配并返回
第一个 visible 的;无可见匹配返回 None。
"""
# 尝试两个时间变体:零点 和 23:59:59部分 checked/selected 格用后者)
for time_variant in (target_time, target_time + 86399000):
sel = f"td div.day[time='{time_variant}']"
cells = frame_locator.locator(sel)
count = cells.count()
for i in range(count):
if cells.nth(i).is_visible():
return cells.nth(i)
return None
def _zto_flip_to_target_month(frame_locator, page, target_time, max_flips=12): def _zto_flip_to_target_month(frame_locator, page, target_time, max_flips=12):
"""中通日历(jQuery-Date-Range-Picker 双月视图)跨月导航:目标日期不在当前视窗时, """中通日历(jQuery-Date-Range-Picker 双月视图)跨月导航:目标日期不在当前视窗时,
循环点 .prev 把目标月翻进 month1 视窗。offset 恒指向过去,故只往前翻;步长 1 月/次 循环点 .prev 把目标月翻进视窗。用 _zto_find_visible_day 判可见(跳过隐藏 ghost cell
用 JS 派发点击(.evaluate("el=>el.click()"))避开 .date-range-length-tip 等 hover 遮挡。
返回 True 若目标格子最终可见。""" 返回 True 若目标格子最终可见。"""
sel = f"td div.day[time='{target_time}']"
for _ in range(max_flips): for _ in range(max_flips):
if frame_locator.locator(sel).first.is_visible(): if _zto_find_visible_day(frame_locator, target_time) is not None:
return True return True
frame_locator.locator(".date-picker-wrapper .prev").first.evaluate( frame_locator.locator(".date-picker-wrapper .prev").first.evaluate(
"el => el.click()" "el => el.click()"
) )
page.wait_for_timeout(450) page.wait_for_timeout(450)
return frame_locator.locator(sel).first.is_visible() return _zto_find_visible_day(frame_locator, target_time) is not None
def zto_smart_menu_click(page, menu_path): def zto_smart_menu_click(page, menu_path):
@@ -170,32 +200,29 @@ def zto_expected_download_impl(page, force=False, date=None):
ewb_frame.locator("#beginDate").click() ewb_frame.locator("#beginDate").click()
page.wait_for_timeout(500) page.wait_for_timeout(500)
today_cell = ewb_frame.locator("td div.day.real-today").first # 直接从 Python datetime 计算目标时间戳,不再依赖 DOM real-today双月视图
today_cell.wait_for(state="visible") # 下 real-today 可能同时出现在 month1 隐藏 ghost cell 和 month2 可见 cell
# .first 会取到隐藏的那个导致 wait_for(visible) 超时)。
target_time = _zto_compute_target_time(offset)
target_cell = _zto_find_visible_day(ewb_frame, target_time)
today_time_str = today_cell.get_attribute("time") if target_cell is None:
if today_time_str: print(" 目标日期不在当前视窗,正在翻月导航 ...")
today_time = int(today_time_str) if not _zto_flip_to_target_month(ewb_frame, page, target_time):
target_time = today_time - offset * 86400000 raise RuntimeError(
target_cell = ewb_frame.locator(f"td div.day[time='{target_time}']").first f"翻月后仍无法定位目标日期格子(time={target_time})"
)
target_cell = _zto_find_visible_day(ewb_frame, target_time)
if target_cell is None:
raise RuntimeError(
f"翻月后仍无法定位目标日期格子(time={target_time})"
)
# 偏移日期跨月时目标格子不在当前双月视窗 → 向前翻月把它带进视窗,不再降级为当天 # 日期格子用 _dom_click 直接派发事件:.click() 会先 hover 格子,触发
if not target_cell.is_visible(): # "范围长度"提示气泡(.date-range-length-tip)盖住格子导致点击被遮挡超时。
print(" 偏移日期跨月,正在向前翻月导航到目标 ...") _dom_click(target_cell)
if not _zto_flip_to_target_month(ewb_frame, page, target_time): page.wait_for_timeout(300)
raise RuntimeError( _dom_click(target_cell)
f"翻月后仍无法定位目标日期格子(time={target_time})"
)
# 日期格子用 _dom_click 直接派发事件(同实到):.click() 会先 hover 格子,触发
# “范围长度”提示气泡(.date-range-length-tip)盖住格子导致点击被遮挡超时,
# 跨月选中非今日格子时尤为明显。
_dom_click(target_cell)
page.wait_for_timeout(300)
_dom_click(target_cell)
else:
today_cell.click()
page.wait_for_timeout(300)
today_cell.click()
page.wait_for_timeout(500) page.wait_for_timeout(500)
@@ -422,31 +449,29 @@ def zto_actual_download_impl(page, date=None):
arr_frame.locator("#daterange").click() arr_frame.locator("#daterange").click()
page.wait_for_timeout(500) page.wait_for_timeout(500)
today_cell = arr_frame.locator("td div.day.real-today").first # 直接从 Python datetime 计算目标时间戳,不再依赖 DOM real-today双月视图
today_cell.wait_for(state="visible") # 下 real-today 可能同时出现在 month1 隐藏 ghost cell 和 month2 可见 cell
# .first 会取到隐藏的那个导致 wait_for(visible) 超时)。
target_time = _zto_compute_target_time(offset)
target_cell = _zto_find_visible_day(arr_frame, target_time)
today_time_str = today_cell.get_attribute("time") if target_cell is None:
if today_time_str: print(" 目标日期不在当前视窗,正在翻月导航 ...")
today_time = int(today_time_str) if not _zto_flip_to_target_month(arr_frame, page, target_time):
target_time = today_time - offset * 86400000 raise RuntimeError(
target_cell = arr_frame.locator(f"td div.day[time='{target_time}']").first f"翻月后仍无法定位目标日期格子(time={target_time})"
)
target_cell = _zto_find_visible_day(arr_frame, target_time)
if target_cell is None:
raise RuntimeError(
f"翻月后仍无法定位目标日期格子(time={target_time})"
)
# 日期格子用 _dom_click 直接派发事件Playwright 的 .click() 会先 hover 格子, # 日期格子用 _dom_click 直接派发事件Playwright 的 .click() 会先 hover 格子,
# 触发范围长度提示气泡(.date-range-length-tip)盖住格子,导致点击被判遮挡而超时。 # 触发"范围长度"提示气泡(.date-range-length-tip)盖住格子,导致点击被判遮挡而超时。
# 偏移日期跨月时目标格子不在当前双月视窗 → 向前翻月把它带进视窗,不再降级为当天。 _dom_click(target_cell)
if not target_cell.is_visible(): page.wait_for_timeout(300)
print(" 偏移日期跨月,正在向前翻月导航到目标 ...") _dom_click(target_cell)
if not _zto_flip_to_target_month(arr_frame, page, target_time):
raise RuntimeError(
f"翻月后仍无法定位目标日期格子(time={target_time})"
)
_dom_click(target_cell)
page.wait_for_timeout(300)
_dom_click(target_cell)
else:
_dom_click(today_cell)
page.wait_for_timeout(300)
_dom_click(today_cell)
page.wait_for_timeout(500) page.wait_for_timeout(500)
@@ -695,7 +720,7 @@ def _zto_poll_and_download_tasks(page, export_times, download_dir, final_filenam
) )
# ==================================================================== # ====================================================================
# 所有目标文件下载完成后,关闭导出任务管理标签页 # 所有目标文件下载完成后,关闭"导出任务管理"标签页
# ==================================================================== # ====================================================================
print(">> 【导出任务管理】下载完成,正在关闭标签页...") print(">> 【导出任务管理】下载完成,正在关闭标签页...")
try: try: