import sys import requests import json import os import re import uuid import hashlib import time import threading from concurrent.futures import ThreadPoolExecutor, as_completed from Crypto.Cipher import AES from Crypto.Util.Padding import unpad from typing import Optional, List, Dict, Any from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry # --- 核心功能类 --- class OrderManager: def __init__(self, access_token, ocr_url): self.access_token = access_token self.ocr_url = ocr_url self.headers = { 'Connection': 'keep-alive', 'content-type': 'application/json', 'Version': '2.50.0', 'timestamp': '1784094158706', 'Distinct-Id': '1784057137891-1059356-086b42dd38b0b98-24491897', 'vaccine': 'aa454c048e8843247cebf378d1a9a0fed759027f329caf68fd900654da4232d0', 'U-A': 'JiDaXiaWeApp/23958 (:)', 'x-version': 'ITC-77385', 'ahs-gate-language': 'zh_CN', 'APP-ID': 'jdx135624', 'platform': 'weapp', 'Register-Flow-Source': 'RECYCLER', 'ahs-language': 'zh-CN', 'access_token': access_token, 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 26_5_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.75(0x18004b3c) NetType/WIFI Language/zh_CN', 'Referer': 'https://servicewechat.com/wx7e6573879e91b1ac/28/page-frame.html', } self.key = b"S9u978T13NLCGc5W" self.iv = b"X83yWMD9iKhLxfwX" # 缓存 self.order_detail_cache = {} self.ocr_cache = {} self.cache_lock = threading.Lock() # 创建会话并配置连接池 self.session = requests.Session() retry_strategy = Retry( total=2, backoff_factor=0.5, status_forcelist=[429, 500, 502, 503, 504], ) adapter = HTTPAdapter( pool_connections=20, pool_maxsize=20, max_retries=retry_strategy ) self.session.mount('http://', adapter) self.session.mount('https://', adapter) def decrypt_order_data(self, hex_string): """解密订单数据""" encrypted_data = bytes.fromhex(hex_string) cipher = AES.new(self.key, AES.MODE_CBC, self.iv) decrypted_data = unpad(cipher.decrypt(encrypted_data), AES.block_size) return decrypted_data.decode('utf-8') def get_pending_order_list(self, page_size=10): """获取待抢订单列表""" url = "https://wirelessgate.aihuishou.com/jdx-rc-bff-service/app/order/pendingGrab/list" payload = { "pageSize": page_size, "status": -1, "isApplyForHandle": None, "applyPhotoStatus": None, "productIds": [265024, 225362, 225361, 225358, 225366, 173473, 161415, 161414, 161413, 161412, 121769, 121439, 121436, 121435, 98526, 98528, 98527, 98525, 43513, 43512, 43511, 43510, 36047, 36045, 36044, 36046, 32292, 32291, 32290, 27640, 27639, 27637, 25827, 25680, 25679, 23423, 23422, 66428, 34701, 20079, 17752, 17726, 17462, 17461, 17459, 17458, 17457, 17460, 17455, 2247, 2246], "sourceIds": None, "keyword": None, "timeObj": None, "rcOrderResult": None } try: response = self.session.post(url, headers=self.headers, json=payload, timeout=30) response.raise_for_status() result = response.json() if result.get('code') != 200: return None, result.get('resultMessage', '未知错误') return result.get('data', []), None except Exception as e: return None, str(e) def get_order_detail(self, order_no): """获取订单详情(带缓存)""" # 检查缓存 with self.cache_lock: if order_no in self.order_detail_cache: return self.order_detail_cache[order_no], None # 获取BiddingNo snatch_url = "https://wirelessgate.aihuishou.com/jdx-rc-service/front/bid/snatch" snatch_payload = {"orderNo": order_no} try: snatch_response = self.session.post(snatch_url, headers=self.headers, json=snatch_payload, timeout=15) snatch_response.raise_for_status() snatch_data = snatch_response.json() if snatch_data.get('code') != 200: return None, snatch_data.get('resultMessage', '获取BiddingNo失败') bidding_no = snatch_data.get('data') if not bidding_no: return None, "获取BiddingNo为空" except Exception as e: return None, str(e) # 获取订单详情 detail_url = f"https://wirelessgate.aihuishou.com/jdx-qa-service/app/ka/v3/order/detail?orderNo={order_no}&biddingNo={bidding_no}" try: detail_response = self.session.get(detail_url, headers=self.headers, timeout=15) detail_response.raise_for_status() detail_data = detail_response.json() if detail_data.get('code') != 200: return None, detail_data.get('resultMessage', '获取订单详情失败') encrypted_hex = detail_data.get('data') if not encrypted_hex: return None, "未获取到加密数据" decrypted_json_string = self.decrypt_order_data(encrypted_hex) order_detail = json.loads(decrypted_json_string) # 存入缓存 with self.cache_lock: self.order_detail_cache[order_no] = order_detail return order_detail, None except Exception as e: return None, str(e) def find_original_image_by_notice(self, json_data, notice_keyword): """优化的图片查找(使用栈迭代)""" if isinstance(json_data, str): try: json_data = json.loads(json_data) except json.JSONDecodeError: return None # 使用栈迭代而非递归 stack = [json_data] keyword_lower = notice_keyword.lower() while stack: obj = stack.pop() if isinstance(obj, dict): # 检查当前字典 if 'notice' in obj and 'originalImage' in obj: if keyword_lower in obj['notice'].lower(): return obj['originalImage'] # 将所有值加入栈 for value in obj.values(): stack.append(value) elif isinstance(obj, list): # 将列表元素加入栈 stack.extend(obj) return None def get_all_image_notices(self, json_data): """获取所有图片的notice信息""" notices = [] stack = [json_data] while stack: obj = stack.pop() if isinstance(obj, dict): if 'notice' in obj and 'image' in obj: notices.append(obj['notice']) for value in obj.values(): stack.append(value) elif isinstance(obj, list): stack.extend(obj) return list(set(notices)) def ocr_by_url(self, image_url): """通过URL进行OCR识别(带缓存)""" # 检查缓存 cache_key = hashlib.md5(image_url.encode()).hexdigest() with self.cache_lock: if cache_key in self.ocr_cache: return self.ocr_cache[cache_key] try: payload = {"url": image_url} response = self.session.post(self.ocr_url, json=payload, timeout=30) if response.status_code == 200: ocr_result = response.json() if ocr_result.get('success'): # 存入缓存 with self.cache_lock: self.ocr_cache[cache_key] = ocr_result return ocr_result return None except Exception as e: return None def extract_text_from_ocr_result(self, ocr_result): """提取OCR文本""" if not ocr_result or not ocr_result.get('texts'): return '' texts = '' for item in ocr_result['texts']: rec_texts = item.get('rec_texts', []) for text in rec_texts: texts = texts + text return texts def extract_charge_count(self, text): """提取充电次数""" # 匹配(232次循环)格式 pattern1 = r'[((]\s*(\d+)\s*次\s*循环\s*[))]' match = re.search(pattern1, text) if match: return int(match.group(1)) # 匹配充电次数123次格式 pattern2 = r'充电次数(\d+)次' match = re.search(pattern2, text) if match: return int(match.group(1)) # 匹配单独的 数字+次 格式(如 71477次) pattern3 = r'(\d+)次' match = re.search(pattern3, text) if match: return int(match.group(1)) return None def find_best_image(self, order_detail): """查找最佳验机图片(按优先级)""" keywords = [ "苹果沙漏验机报告", "沙漏图", "苹果验机软件", "苹果沙漏验机基本信息", "验机照片", "验机报告" ] for keyword in keywords: img = self.find_original_image_by_notice(order_detail, keyword) if img: return img return None # --- 后台工作线程 --- class OrderListWorker(QThread): finished = pyqtSignal(list) error = pyqtSignal(str) def __init__(self, order_manager): super().__init__() self.order_manager = order_manager def run(self): try: order_list, error = self.order_manager.get_pending_order_list(100) if error: self.error.emit(error) else: self.finished.emit(order_list) except Exception as e: self.error.emit(str(e)) class ChargeQueryWorker(QThread): finished = pyqtSignal(int, object, object, list) error = pyqtSignal(int, str) def __init__(self, order_manager, order_no, index): super().__init__() self.order_manager = order_manager self.order_no = order_no self.index = index def run(self): try: # 获取订单详情 order_detail, error = self.order_manager.get_order_detail(self.order_no) if error: self.error.emit(self.index, error) return # 查找所有图片的notice image_notices = self.order_manager.get_all_image_notices(order_detail) # 查找验机图片 report_img = self.order_manager.find_best_image(order_detail) if not report_img: self.finished.emit(self.index, None, None, image_notices) return # OCR识别 ocr_result = self.order_manager.ocr_by_url(report_img) if not ocr_result: self.finished.emit(self.index, None, None, image_notices) return # 提取充电次数 ocr_text = self.order_manager.extract_text_from_ocr_result(ocr_result) charge_count = self.order_manager.extract_charge_count(ocr_text) self.finished.emit(self.index, charge_count, ocr_result, image_notices) except Exception as e: self.error.emit(self.index, str(e)) # --- 主窗口 --- class MainWindow(QMainWindow): def __init__(self): super().__init__() self.order_manager = None self.order_list = [] self.order_details_cache = {} self.charge_counts = {} self.is_batch_processing = False self.batch_queue = [] self.current_batch_index = 0 # 并行处理相关 self.thread_pool = ThreadPoolExecutor(max_workers=5) self.future_tasks = {} self.processing_lock = threading.Lock() self.completed_count = 0 self.total_tasks = 0 self.init_ui() def init_ui(self): self.setWindowTitle('爱回收订单查询工具 (优化版)') self.setGeometry(100, 100, 1200, 800) # 设置样式 self.setStyleSheet(""" QMainWindow { background-color: #f0f0f0; } QGroupBox { font-weight: bold; border: 1px solid #ccc; border-radius: 5px; margin-top: 10px; padding-top: 10px; } QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 5px 0 5px; } QPushButton { background-color: #0078d7; color: white; border: none; padding: 8px 16px; border-radius: 4px; font-weight: bold; } QPushButton:hover { background-color: #005a9e; } QPushButton:disabled { background-color: #cccccc; } QPushButton#batch_btn { background-color: #28a745; } QPushButton#batch_btn:hover { background-color: #218838; } QPushButton#batch_btn:disabled { background-color: #cccccc; } QLineEdit, QTextEdit { border: 1px solid #ccc; border-radius: 4px; padding: 5px; } QTableWidget { border: 1px solid #ccc; border-radius: 4px; gridline-color: #e0e0e0; } QTableWidget::item { padding: 5px; } QHeaderView::section { background-color: #e8e8e8; padding: 5px; border: 1px solid #ccc; font-weight: bold; } QStatusBar { background-color: #f0f0f0; color: #333; } #log_text { background-color: #1e1e1e; color: #d4d4d4; font-family: 'Consolas', 'Courier New', monospace; font-size: 11px; } QProgressBar { border: 1px solid #ccc; border-radius: 4px; text-align: center; } QProgressBar::chunk { background-color: #0078d7; border-radius: 4px; } """) central_widget = QWidget() self.setCentralWidget(central_widget) main_layout = QVBoxLayout(central_widget) main_layout.setSpacing(10) # --- 顶部配置区域 --- config_group = QGroupBox("配置") config_layout = QGridLayout() config_layout.addWidget(QLabel("Access Token:"), 0, 0) self.token_input = QLineEdit() self.token_input.setText("jdx9ce52336abee4cf78db56567dc259512") self.token_input.setEchoMode(QLineEdit.Password) config_layout.addWidget(self.token_input, 0, 1) self.show_token_btn = QPushButton("显示") self.show_token_btn.setFixedWidth(60) self.show_token_btn.clicked.connect(self.toggle_token_visibility) config_layout.addWidget(self.show_token_btn, 0, 2) config_layout.addWidget(QLabel("OCR地址:"), 1, 0) self.ocr_input = QLineEdit() self.ocr_input.setText("http://111.229.45.38:19100/ocr/url") config_layout.addWidget(self.ocr_input, 1, 1, 1, 2) # 并发数设置 config_layout.addWidget(QLabel("并发数:"), 2, 0) self.thread_count_combo = QComboBox() self.thread_count_combo.addItems(["3", "5", "8", "10", "15"]) self.thread_count_combo.setCurrentText("5") config_layout.addWidget(self.thread_count_combo, 2, 1) btn_layout = QHBoxLayout() self.search_btn = QPushButton("🔍 搜索订单") self.search_btn.clicked.connect(self.search_orders) self.search_btn.setFixedHeight(35) btn_layout.addWidget(self.search_btn) self.refresh_btn = QPushButton("🔄 刷新") self.refresh_btn.clicked.connect(self.refresh_orders) self.refresh_btn.setFixedHeight(35) btn_layout.addWidget(self.refresh_btn) self.batch_btn = QPushButton("⚡ 并行查询充电次数") self.batch_btn.setObjectName("batch_btn") self.batch_btn.clicked.connect(self.batch_check_charge_counts_parallel) self.batch_btn.setFixedHeight(35) self.batch_btn.setEnabled(False) btn_layout.addWidget(self.batch_btn) btn_layout.addStretch() config_layout.addLayout(btn_layout, 3, 0, 1, 3) config_group.setLayout(config_layout) main_layout.addWidget(config_group) # --- 订单列表 --- list_group = QGroupBox("订单列表") list_layout = QVBoxLayout() self.progress_bar = QProgressBar() self.progress_bar.setVisible(False) list_layout.addWidget(self.progress_bar) self.table = QTableWidget() self.table.setColumnCount(7) self.table.setHorizontalHeaderLabels(["序号", "订单号", "商品信息", "IMEI", "预估价格", "门店", "充电次数"]) self.table.setColumnWidth(0, 50) self.table.setColumnWidth(1, 200) self.table.setColumnWidth(2, 250) self.table.setColumnWidth(3, 150) self.table.setColumnWidth(4, 100) self.table.setColumnWidth(5, 200) self.table.setColumnWidth(6, 100) self.table.horizontalHeader().setStretchLastSection(True) self.table.verticalHeader().setVisible(False) list_layout.addWidget(self.table) list_group.setLayout(list_layout) main_layout.addWidget(list_group) # --- 日志区域 --- log_group = QGroupBox("日志") log_layout = QVBoxLayout() self.log_text = QTextEdit() self.log_text.setObjectName("log_text") self.log_text.setReadOnly(True) self.log_text.setMaximumHeight(200) log_layout.addWidget(self.log_text) log_btn_layout = QHBoxLayout() self.clear_log_btn = QPushButton("清空日志") self.clear_log_btn.clicked.connect(self.clear_log) log_btn_layout.addWidget(self.clear_log_btn) log_btn_layout.addStretch() log_layout.addLayout(log_btn_layout) log_group.setLayout(log_layout) main_layout.addWidget(log_group) self.status_bar = QStatusBar() self.setStatusBar(self.status_bar) self.status_bar.showMessage("就绪") def toggle_token_visibility(self): if self.token_input.echoMode() == QLineEdit.Password: self.token_input.setEchoMode(QLineEdit.Normal) self.show_token_btn.setText("隐藏") else: self.token_input.setEchoMode(QLineEdit.Password) self.show_token_btn.setText("显示") def log_message(self, message, level="INFO"): timestamp = QDateTime.currentDateTime().toString("yyyy-MM-dd hh:mm:ss") color_map = { "INFO": "#d4d4d4", "WARNING": "#ffa500", "ERROR": "#ff4444", "SUCCESS": "#4caf50", "PROGRESS": "#00bcd4" } color = color_map.get(level, "#d4d4d4") html = f'[{timestamp}] [{level}] {message}
' self.log_text.append(html) scrollbar = self.log_text.verticalScrollBar() scrollbar.setValue(scrollbar.maximum()) def clear_log(self): self.log_text.clear() def search_orders(self): access_token = self.token_input.text().strip() ocr_url = self.ocr_input.text().strip() if not access_token: QMessageBox.warning(self, "提示", "请输入Access Token") return if not ocr_url: QMessageBox.warning(self, "提示", "请输入OCR地址") return self.log_message("开始搜索订单...", "INFO") self.order_manager = OrderManager(access_token, ocr_url) self.order_details_cache = {} self.charge_counts = {} self.batch_btn.setEnabled(False) self.status_bar.showMessage("正在获取订单列表...") self.search_btn.setEnabled(False) self.worker = OrderListWorker(self.order_manager) self.worker.finished.connect(self.on_orders_loaded) self.worker.error.connect(self.on_error) self.worker.start() def refresh_orders(self): if self.order_manager: self.search_orders() else: QMessageBox.warning(self, "提示", "请先搜索订单") def on_orders_loaded(self, order_list): self.order_list = order_list self.display_orders() self.search_btn.setEnabled(True) self.batch_btn.setEnabled(True) self.status_bar.showMessage(f"成功加载 {len(order_list)} 条订单") self.log_message(f"成功加载 {len(order_list)} 条订单", "SUCCESS") def on_error(self, error_msg): self.search_btn.setEnabled(True) self.batch_btn.setEnabled(False) self.status_bar.showMessage("加载失败") self.log_message(f"获取订单列表失败: {error_msg}", "ERROR") QMessageBox.critical(self, "错误", f"获取订单列表失败:\n{error_msg}") def display_orders(self): self.table.setRowCount(len(self.order_list)) for i, order in enumerate(self.order_list): self.table.setItem(i, 0, QTableWidgetItem(str(i + 1))) order_no = order.get('orderNo', '') self.table.setItem(i, 1, QTableWidgetItem(order_no)) brand_info = order.get('brandInfo', '') self.table.setItem(i, 2, QTableWidgetItem(brand_info)) imei = order.get('imei', '') self.table.setItem(i, 3, QTableWidgetItem(imei)) price = order.get('price', '') price_str = str(price) if price else '待定' self.table.setItem(i, 4, QTableWidgetItem(price_str)) title = order.get('title', '') self.table.setItem(i, 5, QTableWidgetItem(title)) charge_widget = QWidget() charge_layout = QHBoxLayout(charge_widget) charge_layout.setContentsMargins(5, 2, 5, 2) charge_layout.setSpacing(5) charge_label = QLabel("未查询") charge_label.setObjectName(f"charge_label_{i}") charge_layout.addWidget(charge_label) check_btn = QPushButton("查询") check_btn.setFixedSize(50, 25) check_btn.setObjectName(f"check_btn_{i}") check_btn.clicked.connect(lambda checked, idx=i: self.check_charge_count(idx)) charge_layout.addWidget(check_btn) charge_widget.setLayout(charge_layout) self.table.setCellWidget(i, 6, charge_widget) def check_charge_count(self, index): if not self.order_manager: return if index >= len(self.order_list): return order_no = self.order_list[index].get('orderNo') if not order_no: return self.log_message(f"开始查询订单 {order_no} 的充电次数", "INFO") self.status_bar.showMessage(f"正在查询订单 {order_no} 的充电次数...") btn = self.findChild(QPushButton, f"check_btn_{index}") if btn: btn.setEnabled(False) btn.setText("查询中") self.charge_worker = ChargeQueryWorker(self.order_manager, order_no, index) self.charge_worker.finished.connect(self.on_charge_queried) self.charge_worker.error.connect(self.on_charge_error) self.charge_worker.start() # ===== 并行批量查询 ===== def batch_check_charge_counts_parallel(self): """并行批量查询充电次数""" if not self.order_list: QMessageBox.warning(self, "提示", "请先搜索订单") return if self.is_batch_processing: return # 检查哪些订单还没查询 pending_indices = [] for i, order in enumerate(self.order_list): order_no = order.get('orderNo') if order_no not in self.charge_counts: pending_indices.append(i) if not pending_indices: self.log_message("所有订单已查询完成", "SUCCESS") QMessageBox.information(self, "提示", "所有订单已查询完成") return # 关闭旧的线程池 if hasattr(self, 'thread_pool'): self.thread_pool.shutdown(wait=False) # 获取并发数 max_workers = int(self.thread_count_combo.currentText()) self.thread_pool = ThreadPoolExecutor(max_workers=max_workers) self.is_batch_processing = True self.completed_count = 0 self.total_tasks = len(pending_indices) self.future_tasks = {} # 禁用按钮 self.batch_btn.setEnabled(False) self.search_btn.setEnabled(False) self.refresh_btn.setEnabled(False) # 显示进度条 self.progress_bar.setVisible(True) self.progress_bar.setMaximum(len(pending_indices)) self.progress_bar.setValue(0) self.log_message(f"开始并行查询 {len(pending_indices)} 个订单 (并发数: {max_workers})", "PROGRESS") # 提交所有任务 token = self.token_input.text().strip() ocr_url = self.ocr_input.text().strip() for idx in pending_indices: order_no = self.order_list[idx].get('orderNo') # 提交任务到线程池 future = self.thread_pool.submit( self.process_single_order_parallel, token, ocr_url, order_no, idx ) self.future_tasks[future] = idx # 更新UI按钮状态 btn = self.findChild(QPushButton, f"check_btn_{idx}") if btn: btn.setEnabled(False) btn.setText("查询中") # 启动结果收集线程 collector = threading.Thread(target=self.collect_parallel_results) collector.daemon = True collector.start() def process_single_order_parallel(self, token, ocr_url, order_no, index): """在独立线程中处理单个订单""" try: # 每个线程创建独立的OrderManager实例 order_manager = OrderManager(token, ocr_url) # 获取订单详情 order_detail, error = order_manager.get_order_detail(order_no) if error: return index, None, error, None # 查找验机图片 report_img = order_manager.find_best_image(order_detail) if not report_img: return index, None, "未找到验机图片", None # OCR识别 ocr_result = order_manager.ocr_by_url(report_img) if not ocr_result: return index, None, "OCR识别失败", None # 提取充电次数 ocr_text = order_manager.extract_text_from_ocr_result(ocr_result) charge_count = order_manager.extract_charge_count(ocr_text) return index, charge_count, None, ocr_result except Exception as e: return index, None, str(e), None def collect_parallel_results(self): """收集并行处理的结果""" for future in as_completed(self.future_tasks): index = self.future_tasks[future] try: idx, charge_count, error, ocr_result = future.result(timeout=60) # 更新UI(在主线程) QMetaObject.invokeMethod(self, "update_parallel_result", Qt.QueuedConnection, Q_ARG(int, idx), Q_ARG(object, charge_count), Q_ARG(str, error or ""), Q_ARG(object, ocr_result) ) except Exception as e: QMetaObject.invokeMethod(self, "update_parallel_result", Qt.QueuedConnection, Q_ARG(int, index), Q_ARG(object, None), Q_ARG(str, str(e)), Q_ARG(object, None) ) self.completed_count += 1 # 更新进度(在主线程) QMetaObject.invokeMethod(self, "update_batch_progress", Qt.QueuedConnection, Q_ARG(int, self.completed_count) ) # 全部完成 QMetaObject.invokeMethod(self, "on_batch_complete_parallel", Qt.QueuedConnection ) @pyqtSlot(int, object, str, object) def update_parallel_result(self, index, charge_count, error, ocr_result): """更新单个结果(在主线程执行)""" order_no = self.order_list[index].get('orderNo') # 缓存结果 if charge_count is not None: self.charge_counts[order_no] = charge_count self.update_charge_display(index, charge_count) self.log_message(f"订单 {order_no} 充电次数: {charge_count}", "SUCCESS") else: self.charge_counts[order_no] = None self.update_charge_display(index, None) if error: self.log_message(f"订单 {order_no} 查询失败: {error}", "ERROR") # 恢复按钮 btn = self.findChild(QPushButton, f"check_btn_{index}") if btn: btn.setEnabled(True) btn.setText("查询") @pyqtSlot(int) def update_batch_progress(self, completed): """更新进度(在主线程执行)""" self.progress_bar.setValue(completed) self.status_bar.showMessage(f"并行处理中: {completed}/{self.total_tasks}") @pyqtSlot() def on_batch_complete_parallel(self): """并行批量完成(在主线程执行)""" self.is_batch_processing = False self.future_tasks.clear() # 恢复按钮 self.batch_btn.setEnabled(True) self.search_btn.setEnabled(True) self.refresh_btn.setEnabled(True) # 隐藏进度条 self.progress_bar.setVisible(False) # 统计结果 success_count = sum(1 for v in self.charge_counts.values() if v is not None) total_count = len(self.charge_counts) self.status_bar.showMessage(f"批量查询完成,成功 {success_count}/{total_count}") self.log_message(f"批量查询完成,成功 {success_count}/{total_count}", "SUCCESS") QMessageBox.information(self, "完成", f"并行批量查询完成!\n\n成功找到充电次数: {success_count}/{total_count}\n总订单: {total_count}") # ===== 原有的串行查询(保留作为备选) ===== def batch_check_charge_counts(self): """串行批量查询(保留)""" if not self.order_list: QMessageBox.warning(self, "提示", "请先搜索订单") return if self.is_batch_processing: return pending_indices = [] for i, order in enumerate(self.order_list): order_no = order.get('orderNo') if order_no not in self.charge_counts: pending_indices.append(i) if not pending_indices: self.log_message("所有订单已查询完成", "SUCCESS") QMessageBox.information(self, "提示", "所有订单已查询完成") return self.is_batch_processing = True self.batch_queue = pending_indices self.current_batch_index = 0 self.batch_btn.setEnabled(False) self.search_btn.setEnabled(False) self.refresh_btn.setEnabled(False) self.progress_bar.setVisible(True) self.progress_bar.setMaximum(len(pending_indices)) self.progress_bar.setValue(0) self.log_message(f"开始串行查询 {len(pending_indices)} 个订单", "PROGRESS") self.process_next_batch() def process_next_batch(self): if self.current_batch_index >= len(self.batch_queue): self.on_batch_complete() return index = self.batch_queue[self.current_batch_index] order_no = self.order_list[index].get('orderNo') self.progress_bar.setValue(self.current_batch_index + 1) self.status_bar.showMessage(f"串行处理中: {self.current_batch_index + 1}/{len(self.batch_queue)}") btn = self.findChild(QPushButton, f"check_btn_{index}") if btn: btn.setEnabled(False) btn.setText("查询中") self.batch_worker = ChargeQueryWorker(self.order_manager, order_no, index) self.batch_worker.finished.connect(self.on_batch_item_complete) self.batch_worker.error.connect(self.on_batch_item_error) self.batch_worker.start() def on_batch_item_complete(self, index, charge_count, ocr_result, image_notices): order_no = self.order_list[index].get('orderNo') self.charge_counts[order_no] = charge_count self.update_charge_display(index, charge_count) btn = self.findChild(QPushButton, f"check_btn_{index}") if btn: btn.setEnabled(True) btn.setText("查询") if charge_count is not None: self.log_message(f"[{self.current_batch_index + 1}/{len(self.batch_queue)}] 订单 {order_no} 充电次数: {charge_count}", "SUCCESS") else: self.log_message(f"[{self.current_batch_index + 1}/{len(self.batch_queue)}] 订单 {order_no} 未找到充电次数", "WARNING") self.current_batch_index += 1 QTimer.singleShot(300, self.process_next_batch) def on_batch_item_error(self, index, error_msg): order_no = self.order_list[index].get('orderNo') self.log_message(f"[{self.current_batch_index + 1}/{len(self.batch_queue)}] 订单 {order_no} 查询失败: {error_msg}", "ERROR") btn = self.findChild(QPushButton, f"check_btn_{index}") if btn: btn.setEnabled(True) btn.setText("查询") self.current_batch_index += 1 QTimer.singleShot(300, self.process_next_batch) def on_batch_complete(self): self.is_batch_processing = False self.batch_queue = [] self.batch_btn.setEnabled(True) self.search_btn.setEnabled(True) self.refresh_btn.setEnabled(True) self.progress_bar.setVisible(False) success_count = sum(1 for v in self.charge_counts.values() if v is not None) total_count = len(self.charge_counts) self.status_bar.showMessage(f"串行查询完成,成功 {success_count}/{total_count}") self.log_message(f"串行查询完成,成功 {success_count}/{total_count}", "SUCCESS") QMessageBox.information(self, "完成", f"串行查询完成!\n\n成功找到充电次数: {success_count}/{total_count}") # ===== 通用方法 ===== def on_charge_queried(self, index, charge_count, ocr_result, image_notices): order_no = self.order_list[index].get('orderNo') if charge_count is None: if ocr_result: self.log_message(f"订单 {order_no} 未找到充电次数,OCR识别结果:", "WARNING") ocr_text = self.order_manager.extract_text_from_ocr_result(ocr_result) if ocr_text: self.log_message(f"OCR文本: {ocr_text}", "INFO") else: self.log_message(f"OCR文本为空", "WARNING") if not image_notices: self.log_message(f"订单 {order_no} 未找到验机图片", "ERROR") else: self.log_message(f"订单 {order_no} 充电次数: {charge_count}", "SUCCESS") self.charge_counts[order_no] = charge_count self.update_charge_display(index, charge_count) btn = self.findChild(QPushButton, f"check_btn_{index}") if btn: btn.setEnabled(True) btn.setText("查询") if charge_count is not None: self.status_bar.showMessage(f"订单 {order_no} 充电次数: {charge_count}") else: self.status_bar.showMessage(f"订单 {order_no} 未找到充电次数信息") def on_charge_error(self, index, error_msg): order_no = self.order_list[index].get('orderNo') self.log_message(f"订单 {order_no} 查询失败: {error_msg}", "ERROR") btn = self.findChild(QPushButton, f"check_btn_{index}") if btn: btn.setEnabled(True) btn.setText("查询") self.status_bar.showMessage(f"查询失败: {error_msg}") QMessageBox.warning(self, "查询失败", f"获取订单详情失败:\n{error_msg}") def update_charge_display(self, index, charge_count): widget = self.table.cellWidget(index, 6) if widget: label = widget.findChild(QLabel) if label: if charge_count is not None: label.setText(f"{charge_count} 次") label.setStyleSheet("color: #28a745; font-weight: bold;") else: label.setText("未找到") label.setStyleSheet("color: #dc3545;") def closeEvent(self, event): """关闭窗口时清理线程池""" if hasattr(self, 'thread_pool'): self.thread_pool.shutdown(wait=False) event.accept() # --- 主程序入口 --- def main(): app = QApplication(sys.argv) app.setStyle('Fusion') window = MainWindow() window.show() sys.exit(app.exec_()) if __name__ == "__main__": main()