|
@@ -17,6 +17,210 @@ from PyQt5.QtGui import *
|
|
|
from requests.adapters import HTTPAdapter
|
|
from requests.adapters import HTTPAdapter
|
|
|
from urllib3.util.retry import Retry
|
|
from urllib3.util.retry import Retry
|
|
|
|
|
|
|
|
|
|
+# --- 图片预览选择对话框 ---
|
|
|
|
|
+class ImageSelectionDialog(QDialog):
|
|
|
|
|
+ """图片选择对话框 - 纯主线程,无定时器,无子线程"""
|
|
|
|
|
+ def __init__(self, image_urls_with_notice, parent=None):
|
|
|
|
|
+ super().__init__(parent)
|
|
|
|
|
+ self.image_urls_with_notice = image_urls_with_notice
|
|
|
|
|
+ self.selected_url = None
|
|
|
|
|
+ self.selected_index = -1
|
|
|
|
|
+ self.skip = False
|
|
|
|
|
+ self.image_cache = {}
|
|
|
|
|
+ self.init_ui()
|
|
|
|
|
+ # 直接加载,不用定时器
|
|
|
|
|
+ self.load_images()
|
|
|
|
|
+
|
|
|
|
|
+ def init_ui(self):
|
|
|
|
|
+ self.setWindowTitle("选择验机图片")
|
|
|
|
|
+ self.setGeometry(200, 200, 900, 600)
|
|
|
|
|
+
|
|
|
|
|
+ main_layout = QVBoxLayout(self)
|
|
|
|
|
+
|
|
|
|
|
+ hint_label = QLabel("请选择要识别的图片(双击预览大图):")
|
|
|
|
|
+ hint_label.setStyleSheet("font-weight: bold; font-size: 12px; padding: 5px;")
|
|
|
|
|
+ main_layout.addWidget(hint_label)
|
|
|
|
|
+
|
|
|
|
|
+ self.list_widget = QListWidget()
|
|
|
|
|
+ self.list_widget.setIconSize(QSize(150, 100))
|
|
|
|
|
+ self.list_widget.setViewMode(QListWidget.IconMode)
|
|
|
|
|
+ self.list_widget.setResizeMode(QListWidget.Adjust)
|
|
|
|
|
+ self.list_widget.setGridSize(QSize(180, 140))
|
|
|
|
|
+ self.list_widget.setSpacing(10)
|
|
|
|
|
+ main_layout.addWidget(self.list_widget)
|
|
|
|
|
+
|
|
|
|
|
+ btn_layout = QHBoxLayout()
|
|
|
|
|
+
|
|
|
|
|
+ self.preview_btn = QPushButton("🔍 预览大图")
|
|
|
|
|
+ self.preview_btn.clicked.connect(self.preview_selected)
|
|
|
|
|
+ btn_layout.addWidget(self.preview_btn)
|
|
|
|
|
+
|
|
|
|
|
+ self.select_btn = QPushButton("✅ 选择此图片识别")
|
|
|
|
|
+ self.select_btn.clicked.connect(self.select_image)
|
|
|
|
|
+ self.select_btn.setStyleSheet("background-color: #28a745; color: white;")
|
|
|
|
|
+ btn_layout.addWidget(self.select_btn)
|
|
|
|
|
+
|
|
|
|
|
+ self.skip_btn = QPushButton("⏭️ 跳过此订单")
|
|
|
|
|
+ self.skip_btn.clicked.connect(self.skip_order)
|
|
|
|
|
+ self.skip_btn.setStyleSheet("background-color: #dc3545; color: white;")
|
|
|
|
|
+ btn_layout.addWidget(self.skip_btn)
|
|
|
|
|
+
|
|
|
|
|
+ self.cancel_btn = QPushButton("❌ 取消全部")
|
|
|
|
|
+ self.cancel_btn.clicked.connect(self.reject)
|
|
|
|
|
+ self.cancel_btn.setStyleSheet("background-color: #6c757d; color: white;")
|
|
|
|
|
+ btn_layout.addWidget(self.cancel_btn)
|
|
|
|
|
+
|
|
|
|
|
+ main_layout.addLayout(btn_layout)
|
|
|
|
|
+
|
|
|
|
|
+ self.status_label = QLabel("正在加载图片...")
|
|
|
|
|
+ self.status_label.setStyleSheet("color: #ffa500; padding: 5px;")
|
|
|
|
|
+ main_layout.addWidget(self.status_label)
|
|
|
|
|
+
|
|
|
|
|
+ # 直接连接信号,不用QueuedConnection
|
|
|
|
|
+ self.list_widget.itemSelectionChanged.connect(self.on_selection_changed)
|
|
|
|
|
+ self.list_widget.itemDoubleClicked.connect(self.preview_selected)
|
|
|
|
|
+
|
|
|
|
|
+ def load_images(self):
|
|
|
|
|
+ """直接在主线程加载图片"""
|
|
|
|
|
+ if not self.image_urls_with_notice:
|
|
|
|
|
+ self.status_label.setText("没有找到图片")
|
|
|
|
|
+ self.status_label.setStyleSheet("color: #dc3545; padding: 5px;")
|
|
|
|
|
+ return
|
|
|
|
|
+
|
|
|
|
|
+ self.status_label.setText(f"正在加载图片... 共 {len(self.image_urls_with_notice)} 张")
|
|
|
|
|
+ QApplication.processEvents()
|
|
|
|
|
+
|
|
|
|
|
+ # 先创建所有列表项
|
|
|
|
|
+ for i, (notice, url) in enumerate(self.image_urls_with_notice):
|
|
|
|
|
+ display_text = notice[:25] + "..." if len(notice) > 25 else notice
|
|
|
|
|
+ item = QListWidgetItem()
|
|
|
|
|
+ item.setText(display_text)
|
|
|
|
|
+ item.setToolTip(f"完整信息: {notice}\nURL: {url}")
|
|
|
|
|
+ item.setData(Qt.UserRole, url)
|
|
|
|
|
+ item.setData(Qt.UserRole + 1, i)
|
|
|
|
|
+ item.setData(Qt.UserRole + 2, notice)
|
|
|
|
|
+ item.setIcon(self.style().standardIcon(QStyle.SP_FileIcon))
|
|
|
|
|
+ self.list_widget.addItem(item)
|
|
|
|
|
+
|
|
|
|
|
+ # 直接在主线程下载图片
|
|
|
|
|
+ loaded_count = 0
|
|
|
|
|
+ for i, (notice, url) in enumerate(self.image_urls_with_notice):
|
|
|
|
|
+ try:
|
|
|
|
|
+ response = requests.get(url, timeout=10)
|
|
|
|
|
+ if response.status_code == 200:
|
|
|
|
|
+ img_data = response.content
|
|
|
|
|
+ pixmap = QPixmap()
|
|
|
|
|
+ pixmap.loadFromData(img_data)
|
|
|
|
|
+ if not pixmap.isNull():
|
|
|
|
|
+ scaled = pixmap.scaled(150, 100, Qt.KeepAspectRatio, Qt.SmoothTransformation)
|
|
|
|
|
+ item = self.list_widget.item(i)
|
|
|
|
|
+ if item:
|
|
|
|
|
+ item.setIcon(QIcon(scaled))
|
|
|
|
|
+ self.image_cache[url] = pixmap
|
|
|
|
|
+ loaded_count += 1
|
|
|
|
|
+ self.status_label.setText(f"加载图片 {i+1}/{len(self.image_urls_with_notice)}")
|
|
|
|
|
+ QApplication.processEvents()
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ print(f"加载图片失败: {e}")
|
|
|
|
|
+
|
|
|
|
|
+ self.status_label.setText(f"加载完成,共加载 {loaded_count}/{len(self.image_urls_with_notice)} 张图片")
|
|
|
|
|
+ self.status_label.setStyleSheet("color: #28a745; padding: 5px;")
|
|
|
|
|
+
|
|
|
|
|
+ if self.list_widget.count() > 0:
|
|
|
|
|
+ self.list_widget.setCurrentRow(0)
|
|
|
|
|
+
|
|
|
|
|
+ def on_selection_changed(self):
|
|
|
|
|
+ selected = self.list_widget.currentItem()
|
|
|
|
|
+ if selected:
|
|
|
|
|
+ notice = selected.data(Qt.UserRole + 2)
|
|
|
|
|
+ self.status_label.setText(f"已选择: {notice[:50]}...")
|
|
|
|
|
+ self.status_label.setStyleSheet("color: #0078d7; padding: 5px;")
|
|
|
|
|
+ else:
|
|
|
|
|
+ self.status_label.setText("请选择一个图片")
|
|
|
|
|
+ self.status_label.setStyleSheet("color: #666; padding: 5px;")
|
|
|
|
|
+
|
|
|
|
|
+ def preview_selected(self):
|
|
|
|
|
+ selected = self.list_widget.currentItem()
|
|
|
|
|
+ if not selected:
|
|
|
|
|
+ QMessageBox.warning(self, "提示", "请先选择一个图片")
|
|
|
|
|
+ return
|
|
|
|
|
+
|
|
|
|
|
+ url = selected.data(Qt.UserRole)
|
|
|
|
|
+ notice = selected.data(Qt.UserRole + 2)
|
|
|
|
|
+
|
|
|
|
|
+ preview_dialog = QDialog(self)
|
|
|
|
|
+ preview_dialog.setWindowTitle(f"图片预览: {notice[:30]}...")
|
|
|
|
|
+ preview_dialog.setGeometry(300, 300, 800, 700)
|
|
|
|
|
+
|
|
|
|
|
+ layout = QVBoxLayout(preview_dialog)
|
|
|
|
|
+
|
|
|
|
|
+ scroll_area = QScrollArea()
|
|
|
|
|
+ scroll_area.setWidgetResizable(True)
|
|
|
|
|
+
|
|
|
|
|
+ label = QLabel()
|
|
|
|
|
+ label.setAlignment(Qt.AlignCenter)
|
|
|
|
|
+ label.setStyleSheet("background-color: #f0f0f0; padding: 10px;")
|
|
|
|
|
+
|
|
|
|
|
+ if url in self.image_cache:
|
|
|
|
|
+ pixmap = self.image_cache[url]
|
|
|
|
|
+ if pixmap and not pixmap.isNull():
|
|
|
|
|
+ scaled = pixmap.scaled(700, 600, Qt.KeepAspectRatio, Qt.SmoothTransformation)
|
|
|
|
|
+ label.setPixmap(scaled)
|
|
|
|
|
+ else:
|
|
|
|
|
+ label.setText("图片加载失败")
|
|
|
|
|
+ else:
|
|
|
|
|
+ try:
|
|
|
|
|
+ response = requests.get(url, timeout=10)
|
|
|
|
|
+ if response.status_code == 200:
|
|
|
|
|
+ pixmap = QPixmap()
|
|
|
|
|
+ pixmap.loadFromData(response.content)
|
|
|
|
|
+ if not pixmap.isNull():
|
|
|
|
|
+ self.image_cache[url] = pixmap
|
|
|
|
|
+ scaled = pixmap.scaled(700, 600, Qt.KeepAspectRatio, Qt.SmoothTransformation)
|
|
|
|
|
+ label.setPixmap(scaled)
|
|
|
|
|
+ else:
|
|
|
|
|
+ label.setText("图片加载失败")
|
|
|
|
|
+ else:
|
|
|
|
|
+ label.setText("图片加载失败")
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ label.setText(f"加载失败")
|
|
|
|
|
+
|
|
|
|
|
+ scroll_area.setWidget(label)
|
|
|
|
|
+ layout.addWidget(scroll_area)
|
|
|
|
|
+
|
|
|
|
|
+ close_btn = QPushButton("关闭")
|
|
|
|
|
+ close_btn.clicked.connect(preview_dialog.accept)
|
|
|
|
|
+ close_btn.setFixedHeight(35)
|
|
|
|
|
+ layout.addWidget(close_btn)
|
|
|
|
|
+
|
|
|
|
|
+ preview_dialog.exec_()
|
|
|
|
|
+
|
|
|
|
|
+ def select_image(self):
|
|
|
|
|
+ selected = self.list_widget.currentItem()
|
|
|
|
|
+ if not selected:
|
|
|
|
|
+ QMessageBox.warning(self, "提示", "请先选择一个图片")
|
|
|
|
|
+ return
|
|
|
|
|
+
|
|
|
|
|
+ self.selected_url = selected.data(Qt.UserRole)
|
|
|
|
|
+ self.selected_index = selected.data(Qt.UserRole + 1)
|
|
|
|
|
+ self.accept()
|
|
|
|
|
+
|
|
|
|
|
+ def skip_order(self):
|
|
|
|
|
+ reply = QMessageBox.question(self, "确认跳过",
|
|
|
|
|
+ "确定要跳过此订单吗?",
|
|
|
|
|
+ QMessageBox.Yes | QMessageBox.No,
|
|
|
|
|
+ QMessageBox.No)
|
|
|
|
|
+ if reply == QMessageBox.Yes:
|
|
|
|
|
+ self.skip = True
|
|
|
|
|
+ self.accept()
|
|
|
|
|
+
|
|
|
|
|
+ def get_selected_url(self):
|
|
|
|
|
+ return self.selected_url
|
|
|
|
|
+
|
|
|
|
|
+ def is_skipped(self):
|
|
|
|
|
+ return self.skip
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
# --- 核心功能类 ---
|
|
# --- 核心功能类 ---
|
|
|
class OrderManager:
|
|
class OrderManager:
|
|
|
def __init__(self, access_token, ocr_url):
|
|
def __init__(self, access_token, ocr_url):
|
|
@@ -43,12 +247,10 @@ class OrderManager:
|
|
|
self.key = b"S9u978T13NLCGc5W"
|
|
self.key = b"S9u978T13NLCGc5W"
|
|
|
self.iv = b"X83yWMD9iKhLxfwX"
|
|
self.iv = b"X83yWMD9iKhLxfwX"
|
|
|
|
|
|
|
|
- # 缓存
|
|
|
|
|
self.order_detail_cache = {}
|
|
self.order_detail_cache = {}
|
|
|
self.ocr_cache = {}
|
|
self.ocr_cache = {}
|
|
|
self.cache_lock = threading.Lock()
|
|
self.cache_lock = threading.Lock()
|
|
|
|
|
|
|
|
- # 创建会话并配置连接池
|
|
|
|
|
self.session = requests.Session()
|
|
self.session = requests.Session()
|
|
|
retry_strategy = Retry(
|
|
retry_strategy = Retry(
|
|
|
total=2,
|
|
total=2,
|
|
@@ -64,14 +266,12 @@ class OrderManager:
|
|
|
self.session.mount('https://', adapter)
|
|
self.session.mount('https://', adapter)
|
|
|
|
|
|
|
|
def decrypt_order_data(self, hex_string):
|
|
def decrypt_order_data(self, hex_string):
|
|
|
- """解密订单数据"""
|
|
|
|
|
encrypted_data = bytes.fromhex(hex_string)
|
|
encrypted_data = bytes.fromhex(hex_string)
|
|
|
cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
|
|
cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
|
|
|
decrypted_data = unpad(cipher.decrypt(encrypted_data), AES.block_size)
|
|
decrypted_data = unpad(cipher.decrypt(encrypted_data), AES.block_size)
|
|
|
return decrypted_data.decode('utf-8')
|
|
return decrypted_data.decode('utf-8')
|
|
|
|
|
|
|
|
def get_pending_order_list(self, page_size=10):
|
|
def get_pending_order_list(self, page_size=10):
|
|
|
- """获取待抢订单列表"""
|
|
|
|
|
url = "https://wirelessgate.aihuishou.com/jdx-rc-bff-service/app/order/pendingGrab/list"
|
|
url = "https://wirelessgate.aihuishou.com/jdx-rc-bff-service/app/order/pendingGrab/list"
|
|
|
|
|
|
|
|
payload = {
|
|
payload = {
|
|
@@ -99,13 +299,10 @@ class OrderManager:
|
|
|
return None, str(e)
|
|
return None, str(e)
|
|
|
|
|
|
|
|
def get_order_detail(self, order_no):
|
|
def get_order_detail(self, order_no):
|
|
|
- """获取订单详情(带缓存)"""
|
|
|
|
|
- # 检查缓存
|
|
|
|
|
with self.cache_lock:
|
|
with self.cache_lock:
|
|
|
if order_no in self.order_detail_cache:
|
|
if order_no in self.order_detail_cache:
|
|
|
return self.order_detail_cache[order_no], None
|
|
return self.order_detail_cache[order_no], None
|
|
|
|
|
|
|
|
- # 获取BiddingNo
|
|
|
|
|
snatch_url = "https://wirelessgate.aihuishou.com/jdx-rc-service/front/bid/snatch"
|
|
snatch_url = "https://wirelessgate.aihuishou.com/jdx-rc-service/front/bid/snatch"
|
|
|
snatch_payload = {"orderNo": order_no}
|
|
snatch_payload = {"orderNo": order_no}
|
|
|
|
|
|
|
@@ -124,7 +321,6 @@ class OrderManager:
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
return None, str(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}"
|
|
detail_url = f"https://wirelessgate.aihuishou.com/jdx-qa-service/app/ka/v3/order/detail?orderNo={order_no}&biddingNo={bidding_no}"
|
|
|
|
|
|
|
|
try:
|
|
try:
|
|
@@ -142,7 +338,6 @@ class OrderManager:
|
|
|
decrypted_json_string = self.decrypt_order_data(encrypted_hex)
|
|
decrypted_json_string = self.decrypt_order_data(encrypted_hex)
|
|
|
order_detail = json.loads(decrypted_json_string)
|
|
order_detail = json.loads(decrypted_json_string)
|
|
|
|
|
|
|
|
- # 存入缓存
|
|
|
|
|
with self.cache_lock:
|
|
with self.cache_lock:
|
|
|
self.order_detail_cache[order_no] = order_detail
|
|
self.order_detail_cache[order_no] = order_detail
|
|
|
|
|
|
|
@@ -152,14 +347,12 @@ class OrderManager:
|
|
|
return None, str(e)
|
|
return None, str(e)
|
|
|
|
|
|
|
|
def find_original_image_by_notice(self, json_data, notice_keyword):
|
|
def find_original_image_by_notice(self, json_data, notice_keyword):
|
|
|
- """优化的图片查找(使用栈迭代)"""
|
|
|
|
|
if isinstance(json_data, str):
|
|
if isinstance(json_data, str):
|
|
|
try:
|
|
try:
|
|
|
json_data = json.loads(json_data)
|
|
json_data = json.loads(json_data)
|
|
|
except json.JSONDecodeError:
|
|
except json.JSONDecodeError:
|
|
|
return None
|
|
return None
|
|
|
|
|
|
|
|
- # 使用栈迭代而非递归
|
|
|
|
|
stack = [json_data]
|
|
stack = [json_data]
|
|
|
keyword_lower = notice_keyword.lower()
|
|
keyword_lower = notice_keyword.lower()
|
|
|
|
|
|
|
@@ -167,23 +360,36 @@ class OrderManager:
|
|
|
obj = stack.pop()
|
|
obj = stack.pop()
|
|
|
|
|
|
|
|
if isinstance(obj, dict):
|
|
if isinstance(obj, dict):
|
|
|
- # 检查当前字典
|
|
|
|
|
if 'notice' in obj and 'originalImage' in obj:
|
|
if 'notice' in obj and 'originalImage' in obj:
|
|
|
if keyword_lower in obj['notice'].lower():
|
|
if keyword_lower in obj['notice'].lower():
|
|
|
return obj['originalImage']
|
|
return obj['originalImage']
|
|
|
|
|
|
|
|
- # 将所有值加入栈
|
|
|
|
|
for value in obj.values():
|
|
for value in obj.values():
|
|
|
stack.append(value)
|
|
stack.append(value)
|
|
|
|
|
|
|
|
elif isinstance(obj, list):
|
|
elif isinstance(obj, list):
|
|
|
- # 将列表元素加入栈
|
|
|
|
|
stack.extend(obj)
|
|
stack.extend(obj)
|
|
|
|
|
|
|
|
return None
|
|
return None
|
|
|
|
|
|
|
|
|
|
+ def get_all_images_with_notice(self, json_data):
|
|
|
|
|
+ images = []
|
|
|
|
|
+ stack = [json_data]
|
|
|
|
|
+
|
|
|
|
|
+ while stack:
|
|
|
|
|
+ obj = stack.pop()
|
|
|
|
|
+
|
|
|
|
|
+ if isinstance(obj, dict):
|
|
|
|
|
+ if 'notice' in obj and 'originalImage' in obj:
|
|
|
|
|
+ images.append((obj['notice'], obj['originalImage']))
|
|
|
|
|
+ for value in obj.values():
|
|
|
|
|
+ stack.append(value)
|
|
|
|
|
+ elif isinstance(obj, list):
|
|
|
|
|
+ stack.extend(obj)
|
|
|
|
|
+
|
|
|
|
|
+ return images
|
|
|
|
|
+
|
|
|
def get_all_image_notices(self, json_data):
|
|
def get_all_image_notices(self, json_data):
|
|
|
- """获取所有图片的notice信息"""
|
|
|
|
|
notices = []
|
|
notices = []
|
|
|
stack = [json_data]
|
|
stack = [json_data]
|
|
|
|
|
|
|
@@ -201,8 +407,6 @@ class OrderManager:
|
|
|
return list(set(notices))
|
|
return list(set(notices))
|
|
|
|
|
|
|
|
def ocr_by_url(self, image_url):
|
|
def ocr_by_url(self, image_url):
|
|
|
- """通过URL进行OCR识别(带缓存)"""
|
|
|
|
|
- # 检查缓存
|
|
|
|
|
cache_key = hashlib.md5(image_url.encode()).hexdigest()
|
|
cache_key = hashlib.md5(image_url.encode()).hexdigest()
|
|
|
with self.cache_lock:
|
|
with self.cache_lock:
|
|
|
if cache_key in self.ocr_cache:
|
|
if cache_key in self.ocr_cache:
|
|
@@ -215,7 +419,6 @@ class OrderManager:
|
|
|
if response.status_code == 200:
|
|
if response.status_code == 200:
|
|
|
ocr_result = response.json()
|
|
ocr_result = response.json()
|
|
|
if ocr_result.get('success'):
|
|
if ocr_result.get('success'):
|
|
|
- # 存入缓存
|
|
|
|
|
with self.cache_lock:
|
|
with self.cache_lock:
|
|
|
self.ocr_cache[cache_key] = ocr_result
|
|
self.ocr_cache[cache_key] = ocr_result
|
|
|
return ocr_result
|
|
return ocr_result
|
|
@@ -225,7 +428,6 @@ class OrderManager:
|
|
|
return None
|
|
return None
|
|
|
|
|
|
|
|
def extract_text_from_ocr_result(self, ocr_result):
|
|
def extract_text_from_ocr_result(self, ocr_result):
|
|
|
- """提取OCR文本"""
|
|
|
|
|
if not ocr_result or not ocr_result.get('texts'):
|
|
if not ocr_result or not ocr_result.get('texts'):
|
|
|
return ''
|
|
return ''
|
|
|
|
|
|
|
@@ -238,20 +440,19 @@ class OrderManager:
|
|
|
return texts
|
|
return texts
|
|
|
|
|
|
|
|
def extract_charge_count(self, text):
|
|
def extract_charge_count(self, text):
|
|
|
- """提取充电次数"""
|
|
|
|
|
- # 匹配(232次循环)格式
|
|
|
|
|
|
|
+ if not text:
|
|
|
|
|
+ return None
|
|
|
|
|
+
|
|
|
pattern1 = r'[((]\s*(\d+)\s*次\s*循环\s*[))]'
|
|
pattern1 = r'[((]\s*(\d+)\s*次\s*循环\s*[))]'
|
|
|
match = re.search(pattern1, text)
|
|
match = re.search(pattern1, text)
|
|
|
if match:
|
|
if match:
|
|
|
return int(match.group(1))
|
|
return int(match.group(1))
|
|
|
|
|
|
|
|
- # 匹配充电次数123次格式
|
|
|
|
|
pattern2 = r'充电次数(\d+)次'
|
|
pattern2 = r'充电次数(\d+)次'
|
|
|
match = re.search(pattern2, text)
|
|
match = re.search(pattern2, text)
|
|
|
if match:
|
|
if match:
|
|
|
return int(match.group(1))
|
|
return int(match.group(1))
|
|
|
|
|
|
|
|
- # 匹配单独的 数字+次 格式(如 71477次)
|
|
|
|
|
pattern3 = r'(\d+)次'
|
|
pattern3 = r'(\d+)次'
|
|
|
match = re.search(pattern3, text)
|
|
match = re.search(pattern3, text)
|
|
|
if match:
|
|
if match:
|
|
@@ -259,8 +460,8 @@ class OrderManager:
|
|
|
|
|
|
|
|
return None
|
|
return None
|
|
|
|
|
|
|
|
- def find_best_image(self, order_detail):
|
|
|
|
|
- """查找最佳验机图片(按优先级)"""
|
|
|
|
|
|
|
+ def find_best_image(self, order_detail, parent_widget=None):
|
|
|
|
|
+ """查找最佳图片,如果找不到则让用户选择"""
|
|
|
keywords = [
|
|
keywords = [
|
|
|
"苹果沙漏验机报告",
|
|
"苹果沙漏验机报告",
|
|
|
"沙漏图",
|
|
"沙漏图",
|
|
@@ -270,11 +471,75 @@ class OrderManager:
|
|
|
"验机报告"
|
|
"验机报告"
|
|
|
]
|
|
]
|
|
|
|
|
|
|
|
|
|
+ # 先尝试用关键词自动匹配
|
|
|
for keyword in keywords:
|
|
for keyword in keywords:
|
|
|
img = self.find_original_image_by_notice(order_detail, keyword)
|
|
img = self.find_original_image_by_notice(order_detail, keyword)
|
|
|
if img:
|
|
if img:
|
|
|
- return img
|
|
|
|
|
- return None
|
|
|
|
|
|
|
+ return img, "auto"
|
|
|
|
|
+
|
|
|
|
|
+ # 自动匹配失败,获取所有图片让用户选择
|
|
|
|
|
+ all_images = self.get_all_images_with_notice(order_detail)
|
|
|
|
|
+
|
|
|
|
|
+ if not all_images:
|
|
|
|
|
+ return None, "no_images"
|
|
|
|
|
+
|
|
|
|
|
+ # 如果只有一张图片,直接返回
|
|
|
|
|
+ if len(all_images) == 1:
|
|
|
|
|
+ return all_images[0][1], "auto_single"
|
|
|
|
|
+
|
|
|
|
|
+ # 多张图片,让用户选择
|
|
|
|
|
+ if parent_widget:
|
|
|
|
|
+ # 使用信号槽方式在主线程显示对话框
|
|
|
|
|
+ result_container = {"action": None, "url": None}
|
|
|
|
|
+ event = threading.Event()
|
|
|
|
|
+
|
|
|
|
|
+ def show_dialog():
|
|
|
|
|
+ try:
|
|
|
|
|
+ dialog = ImageSelectionDialog(all_images, parent_widget)
|
|
|
|
|
+ if dialog.exec_() == QDialog.Accepted:
|
|
|
|
|
+ if dialog.is_skipped():
|
|
|
|
|
+ result_container["action"] = "skip"
|
|
|
|
|
+ else:
|
|
|
|
|
+ selected_url = dialog.get_selected_url()
|
|
|
|
|
+ if selected_url:
|
|
|
|
|
+ result_container["action"] = "selected"
|
|
|
|
|
+ result_container["url"] = selected_url
|
|
|
|
|
+ else:
|
|
|
|
|
+ result_container["action"] = "cancelled"
|
|
|
|
|
+ else:
|
|
|
|
|
+ result_container["action"] = "cancelled"
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ print(f"显示对话框出错: {e}")
|
|
|
|
|
+ result_container["action"] = "error"
|
|
|
|
|
+ finally:
|
|
|
|
|
+ event.set()
|
|
|
|
|
+
|
|
|
|
|
+ # 在主线程执行
|
|
|
|
|
+ if QThread.currentThread() != QCoreApplication.instance().thread():
|
|
|
|
|
+ # 在子线程中,使用invokeMethod
|
|
|
|
|
+ QMetaObject.invokeMethod(parent_widget, "show_image_selection_dialog",
|
|
|
|
|
+ Qt.BlockingQueuedConnection,
|
|
|
|
|
+ Q_ARG(list, all_images),
|
|
|
|
|
+ Q_ARG(dict, result_container),
|
|
|
|
|
+ Q_ARG(threading.Event, event))
|
|
|
|
|
+ else:
|
|
|
|
|
+ # 已经在主线程,直接调用
|
|
|
|
|
+ show_dialog()
|
|
|
|
|
+
|
|
|
|
|
+ # 等待对话框完成
|
|
|
|
|
+ event.wait()
|
|
|
|
|
+
|
|
|
|
|
+ action = result_container.get("action")
|
|
|
|
|
+ if action == "selected":
|
|
|
|
|
+ return result_container.get("url"), "user_selected"
|
|
|
|
|
+ elif action == "skip":
|
|
|
|
|
+ return None, "skipped"
|
|
|
|
|
+ else:
|
|
|
|
|
+ return None, "cancelled"
|
|
|
|
|
+
|
|
|
|
|
+ # 没有父窗口,返回第一张图片
|
|
|
|
|
+ return all_images[0][1], "auto_first"
|
|
|
|
|
+
|
|
|
|
|
|
|
|
# --- 后台工作线程 ---
|
|
# --- 后台工作线程 ---
|
|
|
class OrderListWorker(QThread):
|
|
class OrderListWorker(QThread):
|
|
@@ -299,37 +564,39 @@ class ChargeQueryWorker(QThread):
|
|
|
finished = pyqtSignal(int, object, object, list)
|
|
finished = pyqtSignal(int, object, object, list)
|
|
|
error = pyqtSignal(int, str)
|
|
error = pyqtSignal(int, str)
|
|
|
|
|
|
|
|
- def __init__(self, order_manager, order_no, index):
|
|
|
|
|
|
|
+ def __init__(self, order_manager, order_no, index, parent_widget=None):
|
|
|
super().__init__()
|
|
super().__init__()
|
|
|
self.order_manager = order_manager
|
|
self.order_manager = order_manager
|
|
|
self.order_no = order_no
|
|
self.order_no = order_no
|
|
|
self.index = index
|
|
self.index = index
|
|
|
|
|
+ self.parent_widget = parent_widget
|
|
|
|
|
|
|
|
def run(self):
|
|
def run(self):
|
|
|
try:
|
|
try:
|
|
|
- # 获取订单详情
|
|
|
|
|
order_detail, error = self.order_manager.get_order_detail(self.order_no)
|
|
order_detail, error = self.order_manager.get_order_detail(self.order_no)
|
|
|
if error:
|
|
if error:
|
|
|
self.error.emit(self.index, error)
|
|
self.error.emit(self.index, error)
|
|
|
return
|
|
return
|
|
|
|
|
|
|
|
- # 查找所有图片的notice
|
|
|
|
|
image_notices = self.order_manager.get_all_image_notices(order_detail)
|
|
image_notices = self.order_manager.get_all_image_notices(order_detail)
|
|
|
|
|
|
|
|
- # 查找验机图片
|
|
|
|
|
- report_img = self.order_manager.find_best_image(order_detail)
|
|
|
|
|
|
|
+ # 传入 parent_widget 以便显示对话框
|
|
|
|
|
+ report_img, source = self.order_manager.find_best_image(order_detail, self.parent_widget)
|
|
|
|
|
+
|
|
|
|
|
+ # 处理用户取消或跳过的情况
|
|
|
|
|
+ if source in ["cancelled", "skipped"]:
|
|
|
|
|
+ self.error.emit(self.index, "用户取消选择" if source == "cancelled" else "用户跳过订单")
|
|
|
|
|
+ return
|
|
|
|
|
|
|
|
if not report_img:
|
|
if not report_img:
|
|
|
self.finished.emit(self.index, None, None, image_notices)
|
|
self.finished.emit(self.index, None, None, image_notices)
|
|
|
return
|
|
return
|
|
|
|
|
|
|
|
- # OCR识别
|
|
|
|
|
ocr_result = self.order_manager.ocr_by_url(report_img)
|
|
ocr_result = self.order_manager.ocr_by_url(report_img)
|
|
|
if not ocr_result:
|
|
if not ocr_result:
|
|
|
self.finished.emit(self.index, None, None, image_notices)
|
|
self.finished.emit(self.index, None, None, image_notices)
|
|
|
return
|
|
return
|
|
|
|
|
|
|
|
- # 提取充电次数
|
|
|
|
|
ocr_text = self.order_manager.extract_text_from_ocr_result(ocr_result)
|
|
ocr_text = self.order_manager.extract_text_from_ocr_result(ocr_result)
|
|
|
charge_count = self.order_manager.extract_charge_count(ocr_text)
|
|
charge_count = self.order_manager.extract_charge_count(ocr_text)
|
|
|
|
|
|
|
@@ -350,20 +617,12 @@ class MainWindow(QMainWindow):
|
|
|
self.batch_queue = []
|
|
self.batch_queue = []
|
|
|
self.current_batch_index = 0
|
|
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()
|
|
self.init_ui()
|
|
|
|
|
|
|
|
def init_ui(self):
|
|
def init_ui(self):
|
|
|
- self.setWindowTitle('爱回收订单查询工具 (优化版)')
|
|
|
|
|
|
|
+ self.setWindowTitle('爱回收订单查询工具')
|
|
|
self.setGeometry(100, 100, 1200, 800)
|
|
self.setGeometry(100, 100, 1200, 800)
|
|
|
|
|
|
|
|
- # 设置样式
|
|
|
|
|
self.setStyleSheet("""
|
|
self.setStyleSheet("""
|
|
|
QMainWindow {
|
|
QMainWindow {
|
|
|
background-color: #f0f0f0;
|
|
background-color: #f0f0f0;
|
|
@@ -448,7 +707,6 @@ class MainWindow(QMainWindow):
|
|
|
main_layout = QVBoxLayout(central_widget)
|
|
main_layout = QVBoxLayout(central_widget)
|
|
|
main_layout.setSpacing(10)
|
|
main_layout.setSpacing(10)
|
|
|
|
|
|
|
|
- # --- 顶部配置区域 ---
|
|
|
|
|
config_group = QGroupBox("配置")
|
|
config_group = QGroupBox("配置")
|
|
|
config_layout = QGridLayout()
|
|
config_layout = QGridLayout()
|
|
|
|
|
|
|
@@ -468,13 +726,6 @@ class MainWindow(QMainWindow):
|
|
|
self.ocr_input.setText("http://111.229.45.38:19100/ocr/url")
|
|
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(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()
|
|
btn_layout = QHBoxLayout()
|
|
|
self.search_btn = QPushButton("🔍 搜索订单")
|
|
self.search_btn = QPushButton("🔍 搜索订单")
|
|
|
self.search_btn.clicked.connect(self.search_orders)
|
|
self.search_btn.clicked.connect(self.search_orders)
|
|
@@ -486,9 +737,9 @@ class MainWindow(QMainWindow):
|
|
|
self.refresh_btn.setFixedHeight(35)
|
|
self.refresh_btn.setFixedHeight(35)
|
|
|
btn_layout.addWidget(self.refresh_btn)
|
|
btn_layout.addWidget(self.refresh_btn)
|
|
|
|
|
|
|
|
- self.batch_btn = QPushButton("⚡ 并行查询充电次数")
|
|
|
|
|
|
|
+ self.batch_btn = QPushButton("📋 批量查询充电次数")
|
|
|
self.batch_btn.setObjectName("batch_btn")
|
|
self.batch_btn.setObjectName("batch_btn")
|
|
|
- self.batch_btn.clicked.connect(self.batch_check_charge_counts_parallel)
|
|
|
|
|
|
|
+ self.batch_btn.clicked.connect(self.batch_check_charge_counts)
|
|
|
self.batch_btn.setFixedHeight(35)
|
|
self.batch_btn.setFixedHeight(35)
|
|
|
self.batch_btn.setEnabled(False)
|
|
self.batch_btn.setEnabled(False)
|
|
|
btn_layout.addWidget(self.batch_btn)
|
|
btn_layout.addWidget(self.batch_btn)
|
|
@@ -499,7 +750,6 @@ class MainWindow(QMainWindow):
|
|
|
config_group.setLayout(config_layout)
|
|
config_group.setLayout(config_layout)
|
|
|
main_layout.addWidget(config_group)
|
|
main_layout.addWidget(config_group)
|
|
|
|
|
|
|
|
- # --- 订单列表 ---
|
|
|
|
|
list_group = QGroupBox("订单列表")
|
|
list_group = QGroupBox("订单列表")
|
|
|
list_layout = QVBoxLayout()
|
|
list_layout = QVBoxLayout()
|
|
|
|
|
|
|
@@ -516,7 +766,7 @@ class MainWindow(QMainWindow):
|
|
|
self.table.setColumnWidth(3, 150)
|
|
self.table.setColumnWidth(3, 150)
|
|
|
self.table.setColumnWidth(4, 100)
|
|
self.table.setColumnWidth(4, 100)
|
|
|
self.table.setColumnWidth(5, 200)
|
|
self.table.setColumnWidth(5, 200)
|
|
|
- self.table.setColumnWidth(6, 100)
|
|
|
|
|
|
|
+ self.table.setColumnWidth(6, 120)
|
|
|
self.table.horizontalHeader().setStretchLastSection(True)
|
|
self.table.horizontalHeader().setStretchLastSection(True)
|
|
|
self.table.verticalHeader().setVisible(False)
|
|
self.table.verticalHeader().setVisible(False)
|
|
|
|
|
|
|
@@ -524,7 +774,6 @@ class MainWindow(QMainWindow):
|
|
|
list_group.setLayout(list_layout)
|
|
list_group.setLayout(list_layout)
|
|
|
main_layout.addWidget(list_group)
|
|
main_layout.addWidget(list_group)
|
|
|
|
|
|
|
|
- # --- 日志区域 ---
|
|
|
|
|
log_group = QGroupBox("日志")
|
|
log_group = QGroupBox("日志")
|
|
|
log_layout = QVBoxLayout()
|
|
log_layout = QVBoxLayout()
|
|
|
|
|
|
|
@@ -549,6 +798,29 @@ class MainWindow(QMainWindow):
|
|
|
self.setStatusBar(self.status_bar)
|
|
self.setStatusBar(self.status_bar)
|
|
|
self.status_bar.showMessage("就绪")
|
|
self.status_bar.showMessage("就绪")
|
|
|
|
|
|
|
|
|
|
+ @pyqtSlot(list, dict, threading.Event)
|
|
|
|
|
+ def show_image_selection_dialog(self, all_images, result_container, event):
|
|
|
|
|
+ """在主线程显示图片选择对话框(供invokeMethod调用)"""
|
|
|
|
|
+ try:
|
|
|
|
|
+ dialog = ImageSelectionDialog(all_images, self)
|
|
|
|
|
+ if dialog.exec_() == QDialog.Accepted:
|
|
|
|
|
+ if dialog.is_skipped():
|
|
|
|
|
+ result_container["action"] = "skip"
|
|
|
|
|
+ else:
|
|
|
|
|
+ selected_url = dialog.get_selected_url()
|
|
|
|
|
+ if selected_url:
|
|
|
|
|
+ result_container["action"] = "selected"
|
|
|
|
|
+ result_container["url"] = selected_url
|
|
|
|
|
+ else:
|
|
|
|
|
+ result_container["action"] = "cancelled"
|
|
|
|
|
+ else:
|
|
|
|
|
+ result_container["action"] = "cancelled"
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ print(f"显示图片选择对话框出错: {e}")
|
|
|
|
|
+ result_container["action"] = "error"
|
|
|
|
|
+ finally:
|
|
|
|
|
+ event.set()
|
|
|
|
|
+
|
|
|
def toggle_token_visibility(self):
|
|
def toggle_token_visibility(self):
|
|
|
if self.token_input.echoMode() == QLineEdit.Password:
|
|
if self.token_input.echoMode() == QLineEdit.Password:
|
|
|
self.token_input.setEchoMode(QLineEdit.Normal)
|
|
self.token_input.setEchoMode(QLineEdit.Normal)
|
|
@@ -657,6 +929,8 @@ class MainWindow(QMainWindow):
|
|
|
check_btn = QPushButton("查询")
|
|
check_btn = QPushButton("查询")
|
|
|
check_btn.setFixedSize(50, 25)
|
|
check_btn.setFixedSize(50, 25)
|
|
|
check_btn.setObjectName(f"check_btn_{i}")
|
|
check_btn.setObjectName(f"check_btn_{i}")
|
|
|
|
|
+ # 关键:让按钮不能获得焦点
|
|
|
|
|
+ check_btn.setFocusPolicy(Qt.NoFocus)
|
|
|
check_btn.clicked.connect(lambda checked, idx=i: self.check_charge_count(idx))
|
|
check_btn.clicked.connect(lambda checked, idx=i: self.check_charge_count(idx))
|
|
|
charge_layout.addWidget(check_btn)
|
|
charge_layout.addWidget(check_btn)
|
|
|
|
|
|
|
@@ -680,211 +954,16 @@ class MainWindow(QMainWindow):
|
|
|
btn = self.findChild(QPushButton, f"check_btn_{index}")
|
|
btn = self.findChild(QPushButton, f"check_btn_{index}")
|
|
|
if btn:
|
|
if btn:
|
|
|
btn.setEnabled(False)
|
|
btn.setEnabled(False)
|
|
|
- btn.setText("查询中")
|
|
|
|
|
|
|
+ btn.setText("稍等")
|
|
|
|
|
|
|
|
- self.charge_worker = ChargeQueryWorker(self.order_manager, order_no, index)
|
|
|
|
|
|
|
+ # 传入 self 作为 parent_widget
|
|
|
|
|
+ self.charge_worker = ChargeQueryWorker(self.order_manager, order_no, index, self)
|
|
|
self.charge_worker.finished.connect(self.on_charge_queried)
|
|
self.charge_worker.finished.connect(self.on_charge_queried)
|
|
|
self.charge_worker.error.connect(self.on_charge_error)
|
|
self.charge_worker.error.connect(self.on_charge_error)
|
|
|
self.charge_worker.start()
|
|
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):
|
|
def batch_check_charge_counts(self):
|
|
|
- """串行批量查询(保留)"""
|
|
|
|
|
|
|
+ """串行批量查询充电次数(保留弹窗功能)"""
|
|
|
if not self.order_list:
|
|
if not self.order_list:
|
|
|
QMessageBox.warning(self, "提示", "请先搜索订单")
|
|
QMessageBox.warning(self, "提示", "请先搜索订单")
|
|
|
return
|
|
return
|
|
@@ -915,7 +994,7 @@ class MainWindow(QMainWindow):
|
|
|
self.progress_bar.setMaximum(len(pending_indices))
|
|
self.progress_bar.setMaximum(len(pending_indices))
|
|
|
self.progress_bar.setValue(0)
|
|
self.progress_bar.setValue(0)
|
|
|
|
|
|
|
|
- self.log_message(f"开始串行查询 {len(pending_indices)} 个订单", "PROGRESS")
|
|
|
|
|
|
|
+ self.log_message(f"开始批量查询 {len(pending_indices)} 个订单", "PROGRESS")
|
|
|
self.process_next_batch()
|
|
self.process_next_batch()
|
|
|
|
|
|
|
|
def process_next_batch(self):
|
|
def process_next_batch(self):
|
|
@@ -927,14 +1006,15 @@ class MainWindow(QMainWindow):
|
|
|
order_no = self.order_list[index].get('orderNo')
|
|
order_no = self.order_list[index].get('orderNo')
|
|
|
|
|
|
|
|
self.progress_bar.setValue(self.current_batch_index + 1)
|
|
self.progress_bar.setValue(self.current_batch_index + 1)
|
|
|
- self.status_bar.showMessage(f"串行处理中: {self.current_batch_index + 1}/{len(self.batch_queue)}")
|
|
|
|
|
|
|
+ self.status_bar.showMessage(f"处理中: {self.current_batch_index + 1}/{len(self.batch_queue)}")
|
|
|
|
|
|
|
|
btn = self.findChild(QPushButton, f"check_btn_{index}")
|
|
btn = self.findChild(QPushButton, f"check_btn_{index}")
|
|
|
if btn:
|
|
if btn:
|
|
|
btn.setEnabled(False)
|
|
btn.setEnabled(False)
|
|
|
- btn.setText("查询中")
|
|
|
|
|
|
|
+ btn.setText("稍等")
|
|
|
|
|
|
|
|
- self.batch_worker = ChargeQueryWorker(self.order_manager, order_no, index)
|
|
|
|
|
|
|
+ # 传入 self 作为 parent_widget
|
|
|
|
|
+ self.batch_worker = ChargeQueryWorker(self.order_manager, order_no, index, self)
|
|
|
self.batch_worker.finished.connect(self.on_batch_item_complete)
|
|
self.batch_worker.finished.connect(self.on_batch_item_complete)
|
|
|
self.batch_worker.error.connect(self.on_batch_item_error)
|
|
self.batch_worker.error.connect(self.on_batch_item_error)
|
|
|
self.batch_worker.start()
|
|
self.batch_worker.start()
|
|
@@ -983,12 +1063,11 @@ class MainWindow(QMainWindow):
|
|
|
success_count = sum(1 for v in self.charge_counts.values() if v is not None)
|
|
success_count = sum(1 for v in self.charge_counts.values() if v is not None)
|
|
|
total_count = len(self.charge_counts)
|
|
total_count = len(self.charge_counts)
|
|
|
|
|
|
|
|
- self.status_bar.showMessage(f"串行查询完成,成功 {success_count}/{total_count}")
|
|
|
|
|
- self.log_message(f"串行查询完成,成功 {success_count}/{total_count}", "SUCCESS")
|
|
|
|
|
|
|
+ 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}")
|
|
|
|
|
|
|
+ QMessageBox.information(self, "完成", f"批量查询完成!\n\n成功找到充电次数: {success_count}/{total_count}")
|
|
|
|
|
|
|
|
- # ===== 通用方法 =====
|
|
|
|
|
def on_charge_queried(self, index, charge_count, ocr_result, image_notices):
|
|
def on_charge_queried(self, index, charge_count, ocr_result, image_notices):
|
|
|
order_no = self.order_list[index].get('orderNo')
|
|
order_no = self.order_list[index].get('orderNo')
|
|
|
|
|
|
|
@@ -1029,7 +1108,8 @@ class MainWindow(QMainWindow):
|
|
|
btn.setText("查询")
|
|
btn.setText("查询")
|
|
|
|
|
|
|
|
self.status_bar.showMessage(f"查询失败: {error_msg}")
|
|
self.status_bar.showMessage(f"查询失败: {error_msg}")
|
|
|
- QMessageBox.warning(self, "查询失败", f"获取订单详情失败:\n{error_msg}")
|
|
|
|
|
|
|
+ if error_msg != "用户取消选择" and error_msg != "用户跳过订单":
|
|
|
|
|
+ QMessageBox.warning(self, "查询失败", f"获取订单详情失败:\n{error_msg}")
|
|
|
|
|
|
|
|
def update_charge_display(self, index, charge_count):
|
|
def update_charge_display(self, index, charge_count):
|
|
|
widget = self.table.cellWidget(index, 6)
|
|
widget = self.table.cellWidget(index, 6)
|
|
@@ -1044,9 +1124,6 @@ class MainWindow(QMainWindow):
|
|
|
label.setStyleSheet("color: #dc3545;")
|
|
label.setStyleSheet("color: #dc3545;")
|
|
|
|
|
|
|
|
def closeEvent(self, event):
|
|
def closeEvent(self, event):
|
|
|
- """关闭窗口时清理线程池"""
|
|
|
|
|
- if hasattr(self, 'thread_pool'):
|
|
|
|
|
- self.thread_pool.shutdown(wait=False)
|
|
|
|
|
event.accept()
|
|
event.accept()
|
|
|
|
|
|
|
|
# --- 主程序入口 ---
|
|
# --- 主程序入口 ---
|