fix(anneng): real-type ZK dateboxes + commit-wait before query, skip out-of-range handovers
This commit is contained in:
@@ -11,8 +11,8 @@
|
|||||||
#
|
#
|
||||||
# 应到 = 运单信息导出(每张交接单下应到的运单明细)。整体流程:
|
# 应到 = 运单信息导出(每张交接单下应到的运单明细)。整体流程:
|
||||||
# 主页(重庆鱼洞镇) → 运营管理 → 进站管理 → 进站交接单查询(tab)
|
# 主页(重庆鱼洞镇) → 运营管理 → 进站管理 → 进站交接单查询(tab)
|
||||||
# → 设日期 → 查询 → 等“带小数点的 0”出现(数据加载完毕)
|
# → 设日期 → 等 ZK 组件值提交确认 → 查询 → 等“带小数点的 0”出现(数据加载完毕)
|
||||||
# → 逐条交接单:记下交接单号 → 双击 → 等运单信息加载并核对单号
|
# → 逐条交接单(交接日期超出范围的不导出):记下交接单号 → 双击 → 等运单信息加载并核对单号
|
||||||
# → 导出 → 双击转移全部待选字段 → 导出数据 → 任务添加成功 → 确认 → 回交接单信息
|
# → 导出 → 双击转移全部待选字段 → 导出数据 → 任务添加成功 → 确认 → 回交接单信息
|
||||||
# → 关闭进站交接单查询 tab → 打开导出下载(tab)
|
# → 关闭进站交接单查询 tab → 打开导出下载(tab)
|
||||||
# → 轮询直到本批“交接单明细”任务全部导出完成 → 逐个免对话框下载(x-auth+TGC 直连 GET)
|
# → 轮询直到本批“交接单明细”任务全部导出完成 → 逐个免对话框下载(x-auth+TGC 直连 GET)
|
||||||
@@ -30,7 +30,7 @@ import sys
|
|||||||
import time
|
import time
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import urllib.request
|
import urllib.request
|
||||||
from datetime import datetime, timedelta
|
from datetime import date, datetime, timedelta
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import websocket
|
import websocket
|
||||||
@@ -375,25 +375,57 @@ def wait_query_page_ready(tab_cdp):
|
|||||||
|
|
||||||
|
|
||||||
def set_zk_datebox(tab_cdp, index, date_str):
|
def set_zk_datebox(tab_cdp, index, date_str):
|
||||||
"""直接向第 index 个 z-datebox-inp 写入日期(YYYY-MM-DD),并触发 change。
|
"""向第 index 个 z-datebox-inp 真实键入日期(YYYY-MM-DD),JS blur 触发提交。
|
||||||
|
|
||||||
安能的日期可直接输入(无需日历控件)。ZK datebox 监听 input 的 change 事件。
|
安能日期框是 ZK 组件:直接用原生 setter 改 value + 派发 input/change 事件不会
|
||||||
|
触发 ZK 的 onChange AU(服务端拿不到新值,查询仍用旧范围);必须用 CDP Input 域
|
||||||
|
真实键入(insertText),再 JS blur() 让 ZK 解析、校验并提交 onChange。
|
||||||
|
不用 Tab 按键触发 blur:CDP 按键事件依赖窗口在前台,窗口在后台时会被丢弃。
|
||||||
|
超出页面允许的查询范围时 ZK 会拒绝(不发 onChange),由
|
||||||
|
wait_zk_datebox_committed 等待超时暴露出来。
|
||||||
"""
|
"""
|
||||||
js = (
|
tab_cdp.eval(
|
||||||
"(() => {"
|
"(() => {"
|
||||||
f" const inp = document.querySelectorAll('input.z-datebox-inp')[{index}];"
|
f" const inp = document.querySelectorAll('input.z-datebox-inp')[{index}];"
|
||||||
" if (!inp) return false;"
|
" if (!inp) return false;"
|
||||||
" inp.focus();"
|
" inp.focus();"
|
||||||
" const setter = Object.getOwnPropertyDescriptor("
|
" inp.select();"
|
||||||
" window.HTMLInputElement.prototype, 'value').set;"
|
" return true;"
|
||||||
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;"
|
|
||||||
"})()"
|
"})()"
|
||||||
)
|
)
|
||||||
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):
|
def click_query_button(tab_cdp):
|
||||||
@@ -421,12 +453,54 @@ def wait_query_done(tab_cdp):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def collect_jiaojie_dan_ids(tab_cdp):
|
_JAVA_MONTHS = {
|
||||||
"""收集交接单信息表里所有交接单号(从行复选框的 ewbsListNo 解析,按出现顺序)。
|
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/end(date)时,
|
||||||
|
只保留交接日期在 [start, end] 内的交接单;超期或日期解析失败的只跳过、不中断,
|
||||||
|
数量计入返回的 skipped。交接单号是 19 位纯数字,必须按文本保留,避免精度丢失。
|
||||||
|
返回 (ids, skipped)。
|
||||||
|
"""
|
||||||
|
rows = (
|
||||||
tab_cdp.eval(
|
tab_cdp.eval(
|
||||||
"(() => {"
|
"(() => {"
|
||||||
" const boxes = [...document.querySelectorAll('.z-listbox')];"
|
" const boxes = [...document.querySelectorAll('.z-listbox')];"
|
||||||
@@ -439,14 +513,25 @@ def collect_jiaojie_dan_ids(tab_cdp):
|
|||||||
" const rows = [...box.querySelectorAll('tr.z-listitem')]"
|
" const rows = [...box.querySelectorAll('tr.z-listitem')]"
|
||||||
" .filter(tr => tr.querySelector('input[value*=\"ewbsListNo=\"]'));"
|
" .filter(tr => tr.querySelector('input[value*=\"ewbsListNo=\"]'));"
|
||||||
" return rows.map(tr => {"
|
" return rows.map(tr => {"
|
||||||
" const inp = tr.querySelector('input[value*=\"ewbsListNo=\"]');"
|
" const v = (tr.querySelector('input[value*=\"ewbsListNo=\"]').value) || '';"
|
||||||
" const m = /ewbsListNo=(\\d+)/.exec(inp.value || '');"
|
" const m = /ewbsListNo=(\\d+)/.exec(v);"
|
||||||
" return m ? m[1] : null;"
|
" if (!m) return null;"
|
||||||
|
" const c = /createdTime=([^,\\]]+)/.exec(v);"
|
||||||
|
" return {id: m[1], created: c ? c[1] : null};"
|
||||||
" }).filter(Boolean);"
|
" }).filter(Boolean);"
|
||||||
"})()"
|
"})()"
|
||||||
)
|
)
|
||||||
or []
|
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):
|
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)
|
wait_query_page_ready(tab_cdp)
|
||||||
print("✅ 进站交接单查询页已加载")
|
print("✅ 进站交接单查询页已加载")
|
||||||
|
|
||||||
# 2) 设日期 + 查询 + 等加载
|
# 2) 设日期 + 提交确认 + 查询 + 等加载
|
||||||
print(">> 正在设置查询时间范围(直接输入日期)...")
|
print(">> 正在设置查询时间范围(直接输入日期)...")
|
||||||
set_zk_datebox(tab_cdp, 0, start_str)
|
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)
|
set_zk_datebox(tab_cdp, 1, today_str)
|
||||||
time.sleep(0.3)
|
wait_zk_datebox_committed(tab_cdp, 1, today_str)
|
||||||
print(">> 正在执行查询 ...")
|
print(">> 时间范围已提交确认,正在执行查询 ...")
|
||||||
click_query_button(tab_cdp)
|
click_query_button(tab_cdp)
|
||||||
wait_query_done(tab_cdp)
|
wait_query_done(tab_cdp)
|
||||||
print("✅ 查询完成,数据已加载")
|
print("✅ 查询完成,数据已加载")
|
||||||
|
|
||||||
# 3) 收集交接单号并逐条导出
|
# 3) 收集交接单号并逐条导出
|
||||||
# 行的渲染可能比统计的小数点晚一拍:轮询等行出现;持续为空才视为无数据。
|
# 空结果页面会出现「没有任何相关信息」空态文案;若旧行还没被清掉,
|
||||||
|
# 靠交接日期范围过滤兜底。持续为空才视为无数据。
|
||||||
target_ids = []
|
target_ids = []
|
||||||
|
skipped_ids = 0
|
||||||
collect_deadline = time.monotonic() + 15.0
|
collect_deadline = time.monotonic() + 15.0
|
||||||
while time.monotonic() < collect_deadline:
|
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:
|
if target_ids:
|
||||||
break
|
break
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
|
if skipped_ids:
|
||||||
|
print(
|
||||||
|
f">> 忽略 {skipped_ids} 条超期/日期解析失败的交接单"
|
||||||
|
f"(超出 [{start_str} ~ {today_str}] 范围)"
|
||||||
|
)
|
||||||
print(f">> 共捕获 {len(target_ids)} 条交接单记录")
|
print(f">> 共捕获 {len(target_ids)} 条交接单记录")
|
||||||
if not target_ids:
|
if not target_ids:
|
||||||
print(">> ⚠ 没有交接单数据,结束。")
|
print(">> ⚠ 没有交接单数据,结束。")
|
||||||
|
|||||||
Reference in New Issue
Block a user