fix(anneng): real-type ZK dateboxes + commit-wait before query, skip out-of-range handovers

This commit is contained in:
Misaka_Company
2026-08-04 14:45:57 +08:00
parent 36b204abe5
commit 2387593eda

View File

@@ -11,8 +11,8 @@
#
# 应到 = 运单信息导出(每张交接单下应到的运单明细)。整体流程:
# 主页(重庆鱼洞镇) → 运营管理 → 进站管理 → 进站交接单查询(tab)
# → 设日期 → 查询 → 等“带小数点的 0”出现数据加载完毕
# → 逐条交接单:记下交接单号 → 双击 → 等运单信息加载并核对单号
# → 设日期 → 等 ZK 组件值提交确认 → 查询 → 等“带小数点的 0”出现数据加载完毕
# → 逐条交接单(交接日期超出范围的不导出):记下交接单号 → 双击 → 等运单信息加载并核对单号
# → 导出 → 双击转移全部待选字段 → 导出数据 → 任务添加成功 → 确认 → 回交接单信息
# → 关闭进站交接单查询 tab → 打开导出下载(tab)
# → 轮询直到本批“交接单明细”任务全部导出完成 → 逐个免对话框下载x-auth+TGC 直连 GET
@@ -30,7 +30,7 @@ import sys
import time
import urllib.parse
import urllib.request
from datetime import datetime, timedelta
from datetime import date, datetime, timedelta
import pandas as pd
import websocket
@@ -375,25 +375,57 @@ def wait_query_page_ready(tab_cdp):
def set_zk_datebox(tab_cdp, index, date_str):
"""直接向第 index 个 z-datebox-inp 入日期YYYY-MM-DD并触发 change
"""向第 index 个 z-datebox-inp 真实键入日期YYYY-MM-DDJS blur 触发提交
安能日期可直接输入无需日历控件。ZK datebox 监听 inputchange 事件
安能日期框是 ZK 组件:直接用原生 setter 改 value + 派发 input/change 事件不会
触发 ZK 的 onChange AU服务端拿不到新值查询仍用旧范围必须用 CDP Input 域
真实键入insertText再 JS blur() 让 ZK 解析、校验并提交 onChange。
不用 Tab 按键触发 blurCDP 按键事件依赖窗口在前台,窗口在后台时会被丢弃。
超出页面允许的查询范围时 ZK 会拒绝(不发 onChange
wait_zk_datebox_committed 等待超时暴露出来。
"""
js = (
tab_cdp.eval(
"(() => {"
f" const inp = document.querySelectorAll('input.z-datebox-inp')[{index}];"
" if (!inp) return false;"
" inp.focus();"
" const setter = Object.getOwnPropertyDescriptor("
" window.HTMLInputElement.prototype, 'value').set;"
f" setter.call(inp, {json.dumps(date_str)});"
" inp.dispatchEvent(new Event('input', {bubbles:true}));"
" inp.dispatchEvent(new Event('change', {bubbles:true}));"
" inp.blur();"
" return inp.value;"
" inp.select();"
" return true;"
"})()"
)
return tab_cdp.eval(js)
time.sleep(0.15)
tab_cdp.call("Input.insertText", text=date_str)
time.sleep(0.2)
tab_cdp.eval(
"(() => {"
f" const inp = document.querySelectorAll('input.z-datebox-inp')[{index}];"
" if (inp) inp.blur();"
" return true;"
"})()"
)
def wait_zk_datebox_committed(tab_cdp, index, date_str, timeout=5.0):
"""轮询 ZK 组件值已提交到服务端(读 zk.Widget 的 getText()),确认后再返回。
真实键入 + blur 触发 onChange AU 后服务端接受才会把新值同步回组件setAttr
因此 getText() 等于目标日期即代表服务端已确认。超时说明日期被页面拒绝
(大概率超出该页允许的查询范围),会抛超时报错而不是用旧范围静默查询。
"""
js_date = json.dumps(date_str)
wait_until(
tab_cdp,
"(() => {"
f" const inp = document.querySelectorAll('input.z-datebox-inp')[{index}];"
" if (!inp || typeof zk === 'undefined') return false;"
" const w = zk.Widget.$(inp);"
" if (!w || typeof w.getText !== 'function') return false;"
" const t = String(w.getText() ?? '').trim();"
f" return t === {js_date} || t.split(' ')[0] === {js_date};"
"})()",
f"ZK 日期组件[{index}]已提交 {date_str}",
timeout=timeout,
)
def click_query_button(tab_cdp):
@@ -421,12 +453,54 @@ def wait_query_done(tab_cdp):
)
def collect_jiaojie_dan_ids(tab_cdp):
"""收集交接单信息表里所有交接单号(从行复选框的 ewbsListNo 解析,按出现顺序)。
_JAVA_MONTHS = {
m: i
for i, m in enumerate(
[
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
],
start=1,
)
}
交接单号是 19 位纯数字,必须按文本保留,避免精度丢失。
def _parse_java_date(text):
"""解析 Java Date.toString()(如 'Mon Aug 03 05:50:46 CST 2026')为 date。
固定 token 切分,避免 strptime 对 %Z/%a/%b 的 locale 依赖。
"""
return (
parts = (text or "").split()
if len(parts) != 6:
return None
month = _JAVA_MONTHS.get(parts[1])
if not month:
return None
try:
return date(int(parts[5]), month, int(parts[2]))
except (TypeError, ValueError):
return None
def collect_jiaojie_dan_ids(tab_cdp, start=None, end=None):
"""收集交接单信息表里的交接单号(从行复选框的 ewbsListNo 解析,按出现顺序)。
同时解析每行 value 里的 createdTime交接时间。传 start/enddate
只保留交接日期在 [start, end] 内的交接单;超期或日期解析失败的只跳过、不中断,
数量计入返回的 skipped。交接单号是 19 位纯数字,必须按文本保留,避免精度丢失。
返回 (ids, skipped)。
"""
rows = (
tab_cdp.eval(
"(() => {"
" const boxes = [...document.querySelectorAll('.z-listbox')];"
@@ -439,14 +513,25 @@ def collect_jiaojie_dan_ids(tab_cdp):
" const rows = [...box.querySelectorAll('tr.z-listitem')]"
" .filter(tr => tr.querySelector('input[value*=\"ewbsListNo=\"]'));"
" return rows.map(tr => {"
" const inp = tr.querySelector('input[value*=\"ewbsListNo=\"]');"
" const m = /ewbsListNo=(\\d+)/.exec(inp.value || '');"
" return m ? m[1] : null;"
" const v = (tr.querySelector('input[value*=\"ewbsListNo=\"]').value) || '';"
" const m = /ewbsListNo=(\\d+)/.exec(v);"
" if (!m) return null;"
" const c = /createdTime=([^,\\]]+)/.exec(v);"
" return {id: m[1], created: c ? c[1] : null};"
" }).filter(Boolean);"
"})()"
)
or []
)
ids, skipped = [], 0
for rec in rows:
created = _parse_java_date(rec.get("created")) if rec.get("created") else None
if start is not None and end is not None:
if created is None or not (start <= created <= end):
skipped += 1
continue
ids.append(rec["id"])
return ids, skipped
def dblclick_jiaojie_dan_row(tab_cdp, ewbs_no):
@@ -947,25 +1032,41 @@ def anneng_expected_download_impl(force=False, date=None):
wait_query_page_ready(tab_cdp)
print("✅ 进站交接单查询页已加载")
# 2) 设日期 + 查询 + 等加载
# 2) 设日期 + 提交确认 + 查询 + 等加载
print(">> 正在设置查询时间范围(直接输入日期)...")
set_zk_datebox(tab_cdp, 0, start_str)
# ZK 组件值异步提交:逐个设置并等服务端确认后再设下一个,
# 避免前一个 datebox 的 AU 响应与后一个的键入互相干扰(曾导致偶发提交超时);
# 未确认前点查询会按旧的时间范围查询(安能页面特性)。
wait_zk_datebox_committed(tab_cdp, 0, start_str)
set_zk_datebox(tab_cdp, 1, today_str)
time.sleep(0.3)
print(">> 正在执行查询 ...")
wait_zk_datebox_committed(tab_cdp, 1, today_str)
print(">> 时间范围已提交确认,正在执行查询 ...")
click_query_button(tab_cdp)
wait_query_done(tab_cdp)
print("✅ 查询完成,数据已加载")
# 3) 收集交接单号并逐条导出
# 行的渲染可能比统计的小数点晚一拍:轮询等行出现;持续为空才视为无数据。
# 空结果页面会出现「没有任何相关信息」空态文案;若旧行还没被清掉,
# 靠交接日期范围过滤兜底。持续为空才视为无数据。
target_ids = []
skipped_ids = 0
collect_deadline = time.monotonic() + 15.0
while time.monotonic() < collect_deadline:
target_ids = collect_jiaojie_dan_ids(tab_cdp)
if tab_cdp.eval("document.body.innerText.includes('没有任何相关信息')"):
print(">> 查询完成但无交接单数据(页面空态),结束。")
return
target_ids, skipped_ids = collect_jiaojie_dan_ids(
tab_cdp, start=target.date(), end=target.date()
)
if target_ids:
break
time.sleep(0.5)
if skipped_ids:
print(
f">> 忽略 {skipped_ids} 条超期/日期解析失败的交接单"
f"(超出 [{start_str} ~ {today_str}] 范围)"
)
print(f">> 共捕获 {len(target_ids)} 条交接单记录")
if not target_ids:
print(">> ⚠ 没有交接单数据,结束。")