From 86898265c05451ac072200c4b2e761ebdad01004 Mon Sep 17 00:00:00 2001 From: hjq <770690987@qq.com> Date: Fri, 12 Jun 2026 16:30:57 +0800 Subject: [PATCH] =?UTF-8?q?Dockerfile=20=E9=83=A8=E7=BD=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fetch_issue_receipt_incremental.py | 177 ++++++++++-------- web_ui/app.py | 38 ++++ 2 files changed, 138 insertions(+), 77 deletions(-) diff --git a/browser_login/fetch_issue_receipt_incremental.py b/browser_login/fetch_issue_receipt_incremental.py index 962598c..3b5de0e 100644 --- a/browser_login/fetch_issue_receipt_incremental.py +++ b/browser_login/fetch_issue_receipt_incremental.py @@ -28,7 +28,7 @@ def get_local_count(conn): return cursor.fetchone()[0] def item_exists(cursor, item): - """判断某条发料明细是否已在数据库中存在(基于发料单号+行号+物料代码组合判断)""" + """判断某条发料明细是否已在数据库中存在(基于 发料单号 + 行号 + 物料代码 组合判断)""" wo_number = item.get("workOrdersNumber") line_no = item.get("lineNumber") mat_code = item.get("materialCode") @@ -98,49 +98,61 @@ def fetch_issue_receipt_incremental(): page = get_page(port=9222) try: - log("INFO", f"正在回到主页起点: {HOME_URL}") - page.get(HOME_URL) + log("INFO", f"正在直接跳转到发料单明细页面...") + page.get("https://yunmes.tftykj.cn/WorkOrdersQuery") page.wait.load_start() time.sleep(2) - menus = [ - ("第一层: 业务统计报表", 'xpath://*[@id="app"]/div/div[1]/div[1]/div[2]/div/div[1]/div/div[10]/div/p'), - ("第二层: 生产业务报表(推测)", 'xpath:/html/body/div[7]/div/div[1]/div/div[9]/div/p'), - ("第三层: 发料单报表", 'xpath:/html/body/div[8]/div/div[1]/div/div[6]/div/p') - ] - - log("INFO", "模拟点击左侧导航菜单...") - for name, xpath in menus: - ele = page.ele(xpath, timeout=5) - if ele: - try: ele.click() - except: page.run_js("arguments[0].click();", ele) - time.sleep(1.5) - else: - log("ERR", f"找不到菜单元素: {name}") - return - - log("OK", "✅ 成功点开发料单报表界面!") - - # 隐藏菜单 - blank_xpath = 'xpath://*[@id="app"]/div/div[1]/div[2]/div[1]/div[2]/div[2]/div/div[1]/div' - blank_ele = page.ele(blank_xpath, timeout=3) - if blank_ele: - try: blank_ele.click() - except: page.run_js("arguments[0].click();", blank_ele) - time.sleep(0.5) - log("INFO", f"开启底层数据拦截网: {API_TARGET}") page.listen.start(API_TARGET) + # 为了能够获取当月的数据,强制设置时间为当月第一天到最后一天,并清理其他条件 + import datetime, calendar + now = datetime.datetime.now() + first_day = datetime.date(now.year, now.month, 1).strftime('%Y-%m-%d') + last_day = datetime.date(now.year, now.month, calendar.monthrange(now.year, now.month)[1]).strftime('%Y-%m-%d') + + log("INFO", f"正在自动设置查询时间范围: {first_day} 至 {last_day}") + + page.run_js(f""" + try {{ + var dates = document.querySelectorAll('.datebox-f, .datetimebox-f, .el-date-editor input'); + if (dates.length >= 2) {{ + dates[0].value = '{first_day}'; + dates[1].value = '{last_day}'; + dates[0].dispatchEvent(new Event('input', {{ bubbles: true }})); + dates[0].dispatchEvent(new Event('change', {{ bubbles: true }})); + dates[1].dispatchEvent(new Event('input', {{ bubbles: true }})); + dates[1].dispatchEvent(new Event('change', {{ bubbles: true }})); + }} + }} catch(e) {{ console.log(e); }} + """) + time.sleep(1) + + # 因为是直接打开 URL,数据通常不会自动加载,所以尝试点击查询按钮 packet = page.listen.wait(timeout=10) if not packet: - query_btn_xpath = 'xpath://*[@id="app"]/div/div[1]/div[2]/div[2]/div[1]/div[1]/div/button[1]/span' - query_btn = page.ele(query_btn_xpath, timeout=3) + # 兼容多种查询按钮的查找方式 + log("INFO", "尝试寻找并点击页面上的【查询】按钮...") + query_btn = page.ele('text=查询', timeout=3) + if not query_btn: + query_btn = page.ele('xpath://button[contains(., "查询")]', timeout=3) + if query_btn: try: query_btn.click() except: page.run_js("arguments[0].click();", query_btn) - packet = page.listen.wait(timeout=15) + else: + log("WARN", "常规选择器找不到查询按钮,尝试使用全局 JS 强行寻找...") + page.run_js(""" + var btns = document.querySelectorAll('button, a, .l-btn, .el-button'); + for(var i=0; i 1: + log("OK", "🎉 本页未发现任何新数据,说明增量部分已全部抓取完毕,停止翻页!") + break + # 如果没遇到旧数据,继续点击下一页 delay = random.uniform(1.5, 3.5) log("INFO", f"⏳ 停顿 {delay:.2f} 秒后点击下一页...") @@ -245,7 +253,10 @@ def fetch_issue_receipt_incremental(): next_btn = None for _ in range(3): - next_btn = page.ele('xpath://button[contains(@class, "btn-next")]', timeout=3) + # 优先使用 pagination-next,如果不行再尝试 btn-next + next_btn = page.ele('xpath://*[contains(@class, "pagination-next")]', timeout=3) + if not next_btn: + next_btn = page.ele('xpath://button[contains(@class, "btn-next")]', timeout=3) if next_btn: break time.sleep(1) @@ -259,12 +270,24 @@ def fetch_issue_receipt_incremental(): aria_disabled = next_btn.attr("aria-disabled") is_disabled_attr = next_btn.attr("disabled") is not None - if "disabled" in class_str or is_disabled_attr or aria_disabled == "true": + # 检查父元素
  • 是否被禁用 + parent_class_str = "" + try: + parent_ele = next_btn.parent() + parent_class_str = str(parent_ele.attr("class")) + except: + pass + + if "disabled" in class_str or "disabled" in parent_class_str or is_disabled_attr or aria_disabled == "true": log("OK", "🏁 下一页按钮已被禁用,已经翻到最后一页。") break - try: next_btn.click() - except: page.run_js("arguments[0].click();", next_btn) + try: + # 优先使用 JS 点击防止遮挡 + page.run_js("arguments[0].click();", next_btn) + except Exception as e: + log("ERR", f"JS 点击失败: {e},尝试普通点击...") + next_btn.click() packet = page.listen.wait(timeout=15) if not packet: diff --git a/web_ui/app.py b/web_ui/app.py index 6be9777..58744a9 100644 --- a/web_ui/app.py +++ b/web_ui/app.py @@ -435,6 +435,44 @@ def sync_receipts(): except Exception as e: return jsonify({"success": False, "message": f"系统错误: {str(e)}"}), 500 +@app.route('/api/sync_issue_receipts', methods=['POST']) +def sync_issue_receipts(): + """触发后台运行发料单明细增量抓取脚本""" + global is_browser_busy + import sys + + if is_browser_busy: + return jsonify({ + "success": False, + "message": f"系统忙碌:当前正在执行 '{current_task_name}',请稍后再试。" + }), 409 + + if str(BROWSER_LOGIN_DIR) not in sys.path: + sys.path.insert(0, str(BROWSER_LOGIN_DIR)) + + def run_issue_receipt_sync(): + set_browser_busy("手动发料单明细增量同步") + try: + from fetch_issue_receipt_incremental import fetch_issue_receipt_incremental + fetch_issue_receipt_incremental() + except Exception as e: + print(f"发料单明细增量同步失败: {e}") + finally: + release_browser() + + try: + threading.Thread(target=run_issue_receipt_sync, daemon=True).start() + + return jsonify({ + "success": True, + "message": "发料单明细增量抓取任务已在后台启动!请观察运行日志。", + "logs": "任务已在后台运行..." + }) + except ImportError: + return jsonify({"success": False, "message": "找不到发料单增量抓取脚本或导入失败"}), 404 + except Exception as e: + return jsonify({"success": False, "message": f"系统错误: {str(e)}"}), 500 + @app.route('/api/sync_work_orders', methods=['POST']) def sync_work_orders(): """触发后台运行生产工单增量抓取脚本"""