解密机大侠.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710
  1. import sys
  2. import requests
  3. import json
  4. import os
  5. import re
  6. import uuid
  7. import hashlib
  8. from Crypto.Cipher import AES
  9. from Crypto.Util.Padding import unpad
  10. from typing import Optional, List, Dict, Any
  11. from PyQt5.QtWidgets import *
  12. from PyQt5.QtCore import *
  13. from PyQt5.QtGui import *
  14. # --- 核心功能类 ---
  15. class OrderManager:
  16. def __init__(self, access_token, ocr_url):
  17. self.access_token = access_token
  18. self.ocr_url = ocr_url
  19. self.headers = {
  20. 'Connection': 'keep-alive',
  21. 'content-type': 'application/json',
  22. 'Version': '2.50.0',
  23. 'timestamp': '1784094158706',
  24. 'Distinct-Id': '1784057137891-1059356-086b42dd38b0b98-24491897',
  25. 'vaccine': 'aa454c048e8843247cebf378d1a9a0fed759027f329caf68fd900654da4232d0',
  26. 'U-A': 'JiDaXiaWeApp/23958 (:)',
  27. 'x-version': 'ITC-77385',
  28. 'ahs-gate-language': 'zh_CN',
  29. 'APP-ID': 'jdx135624',
  30. 'platform': 'weapp',
  31. 'Register-Flow-Source': 'RECYCLER',
  32. 'ahs-language': 'zh-CN',
  33. 'access_token': access_token,
  34. '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',
  35. 'Referer': 'https://servicewechat.com/wx7e6573879e91b1ac/28/page-frame.html',
  36. }
  37. self.key = b"S9u978T13NLCGc5W"
  38. self.iv = b"X83yWMD9iKhLxfwX"
  39. def decrypt_order_data(self, hex_string):
  40. """解密订单数据"""
  41. encrypted_data = bytes.fromhex(hex_string)
  42. cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
  43. decrypted_data = unpad(cipher.decrypt(encrypted_data), AES.block_size)
  44. return decrypted_data.decode('utf-8')
  45. def get_pending_order_list(self, page_size=10):
  46. """获取待抢订单列表"""
  47. url = "https://wirelessgate.aihuishou.com/jdx-rc-bff-service/app/order/pendingGrab/list"
  48. payload = {
  49. "pageSize": page_size,
  50. "status": -1,
  51. "isApplyForHandle": None,
  52. "applyPhotoStatus": None,
  53. "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],
  54. "sourceIds": None,
  55. "keyword": None,
  56. "timeObj": None,
  57. "rcOrderResult": None
  58. }
  59. try:
  60. response = requests.post(url, headers=self.headers, json=payload, timeout=30)
  61. response.raise_for_status()
  62. result = response.json()
  63. if result.get('code') != 200:
  64. return None, result.get('resultMessage', '未知错误')
  65. return result.get('data', []), None
  66. except Exception as e:
  67. return None, str(e)
  68. def get_order_detail(self, order_no):
  69. """获取订单详情"""
  70. # 获取BiddingNo
  71. snatch_url = "https://wirelessgate.aihuishou.com/jdx-rc-service/front/bid/snatch"
  72. snatch_payload = {"orderNo": order_no}
  73. try:
  74. snatch_response = requests.post(snatch_url, headers=self.headers, json=snatch_payload, timeout=30)
  75. snatch_response.raise_for_status()
  76. snatch_data = snatch_response.json()
  77. if snatch_data.get('code') != 200:
  78. return None, snatch_data.get('resultMessage', '获取BiddingNo失败')
  79. bidding_no = snatch_data.get('data')
  80. if not bidding_no:
  81. return None, "获取BiddingNo为空"
  82. except Exception as e:
  83. return None, str(e)
  84. # 获取订单详情
  85. detail_url = f"https://wirelessgate.aihuishou.com/jdx-qa-service/app/ka/v3/order/detail?orderNo={order_no}&biddingNo={bidding_no}"
  86. try:
  87. detail_response = requests.get(detail_url, headers=self.headers, timeout=30)
  88. detail_response.raise_for_status()
  89. detail_data = detail_response.json()
  90. if detail_data.get('code') != 200:
  91. return None, detail_data.get('resultMessage', '获取订单详情失败')
  92. encrypted_hex = detail_data.get('data')
  93. if not encrypted_hex:
  94. return None, "未获取到加密数据"
  95. decrypted_json_string = self.decrypt_order_data(encrypted_hex)
  96. order_detail = json.loads(decrypted_json_string)
  97. return order_detail, None
  98. except Exception as e:
  99. return None, str(e)
  100. def find_original_image_by_notice(self, json_data, notice_keyword):
  101. """查找图片链接"""
  102. if isinstance(json_data, str):
  103. try:
  104. json_data = json.loads(json_data)
  105. except json.JSONDecodeError:
  106. return None
  107. def recursive_search(obj):
  108. if isinstance(obj, dict):
  109. if 'notice' in obj and 'originalImage' in obj:
  110. if notice_keyword.lower() in obj['notice'].lower():
  111. return obj['originalImage']
  112. for value in obj.values():
  113. result = recursive_search(value)
  114. if result:
  115. return result
  116. elif isinstance(obj, list):
  117. for item in obj:
  118. result = recursive_search(item)
  119. if result:
  120. return result
  121. return None
  122. return recursive_search(json_data)
  123. def get_all_image_notices(self, json_data):
  124. """获取所有图片的notice信息"""
  125. notices = []
  126. def recursive_search(obj):
  127. if isinstance(obj, dict):
  128. if 'notice' in obj and 'image' in obj:
  129. notices.append(obj['notice'])
  130. for value in obj.values():
  131. recursive_search(value)
  132. elif isinstance(obj, list):
  133. for item in obj:
  134. recursive_search(item)
  135. recursive_search(json_data)
  136. return list(set(notices)) # 去重
  137. def download_and_ocr_image(self, image_url):
  138. """下载图片并OCR识别"""
  139. try:
  140. # 下载图片
  141. response = requests.get(image_url, timeout=30)
  142. response.raise_for_status()
  143. # 创建temp目录
  144. os.makedirs('./temp', exist_ok=True)
  145. # 保存临时文件
  146. url_path = image_url.split('?')[0]
  147. file_ext = os.path.splitext(url_path)[1]
  148. if not file_ext:
  149. file_ext = '.jpg'
  150. random_filename = f"{uuid.uuid4().hex}{file_ext}"
  151. temp_path = os.path.join('./temp', random_filename)
  152. with open(temp_path, 'wb') as f:
  153. f.write(response.content)
  154. # 发送到OCR服务
  155. with open(temp_path, 'rb') as f:
  156. files = {'image': (random_filename, f, 'image/jpeg')}
  157. ocr_response = requests.post(self.ocr_url, files=files, timeout=60)
  158. # 清理临时文件
  159. if os.path.exists(temp_path):
  160. os.remove(temp_path)
  161. if ocr_response.status_code == 200:
  162. ocr_result = ocr_response.json()
  163. if ocr_result.get('success'):
  164. return ocr_result
  165. return None
  166. except Exception as e:
  167. return None
  168. def extract_text_from_ocr_result(self, ocr_result):
  169. """提取OCR文本"""
  170. if not ocr_result or not ocr_result.get('texts'):
  171. return ''
  172. texts = ''
  173. for item in ocr_result['texts']:
  174. rec_texts = item.get('rec_texts', [])
  175. for text in rec_texts:
  176. texts = texts + text
  177. return texts
  178. def extract_charge_count(self, text):
  179. """提取充电次数"""
  180. pattern = r'充电次数(\d+)次'
  181. match = re.search(pattern, text)
  182. if match:
  183. return int(match.group(1))
  184. return None
  185. # --- GUI主窗口 ---
  186. class MainWindow(QMainWindow):
  187. def __init__(self):
  188. super().__init__()
  189. self.order_manager = None
  190. self.order_list = []
  191. self.order_details_cache = {} # 缓存订单详情
  192. self.charge_counts = {} # 缓存充电次数
  193. self.init_ui()
  194. def init_ui(self):
  195. self.setWindowTitle('爱回收订单查询工具')
  196. self.setGeometry(100, 100, 1200, 800)
  197. # 设置样式
  198. self.setStyleSheet("""
  199. QMainWindow {
  200. background-color: #f0f0f0;
  201. }
  202. QGroupBox {
  203. font-weight: bold;
  204. border: 1px solid #ccc;
  205. border-radius: 5px;
  206. margin-top: 10px;
  207. padding-top: 10px;
  208. }
  209. QGroupBox::title {
  210. subcontrol-origin: margin;
  211. left: 10px;
  212. padding: 0 5px 0 5px;
  213. }
  214. QPushButton {
  215. background-color: #0078d7;
  216. color: white;
  217. border: none;
  218. padding: 8px 16px;
  219. border-radius: 4px;
  220. font-weight: bold;
  221. }
  222. QPushButton:hover {
  223. background-color: #005a9e;
  224. }
  225. QPushButton:disabled {
  226. background-color: #cccccc;
  227. }
  228. QLineEdit, QTextEdit {
  229. border: 1px solid #ccc;
  230. border-radius: 4px;
  231. padding: 5px;
  232. }
  233. QTableWidget {
  234. border: 1px solid #ccc;
  235. border-radius: 4px;
  236. gridline-color: #e0e0e0;
  237. }
  238. QTableWidget::item {
  239. padding: 5px;
  240. }
  241. QHeaderView::section {
  242. background-color: #e8e8e8;
  243. padding: 5px;
  244. border: 1px solid #ccc;
  245. font-weight: bold;
  246. }
  247. QStatusBar {
  248. background-color: #f0f0f0;
  249. color: #333;
  250. }
  251. #log_text {
  252. background-color: #1e1e1e;
  253. color: #d4d4d4;
  254. font-family: 'Consolas', 'Courier New', monospace;
  255. font-size: 11px;
  256. }
  257. """)
  258. # 中央部件
  259. central_widget = QWidget()
  260. self.setCentralWidget(central_widget)
  261. main_layout = QVBoxLayout(central_widget)
  262. main_layout.setSpacing(10)
  263. # --- 顶部配置区域 ---
  264. config_group = QGroupBox("配置")
  265. config_layout = QGridLayout()
  266. # Token输入
  267. config_layout.addWidget(QLabel("Access Token:"), 0, 0)
  268. self.token_input = QLineEdit()
  269. self.token_input.setText("jdx9ce52336abee4cf78db56567dc259512")
  270. self.token_input.setEchoMode(QLineEdit.Password)
  271. config_layout.addWidget(self.token_input, 0, 1)
  272. # 显示/隐藏Token按钮
  273. self.show_token_btn = QPushButton("显示")
  274. self.show_token_btn.setFixedWidth(60)
  275. self.show_token_btn.clicked.connect(self.toggle_token_visibility)
  276. config_layout.addWidget(self.show_token_btn, 0, 2)
  277. # OCR地址输入
  278. config_layout.addWidget(QLabel("OCR地址:"), 1, 0)
  279. self.ocr_input = QLineEdit()
  280. self.ocr_input.setText("http://192.168.0.110:10001/ocr")
  281. config_layout.addWidget(self.ocr_input, 1, 1, 1, 2)
  282. # 操作按钮
  283. btn_layout = QHBoxLayout()
  284. self.search_btn = QPushButton("🔍 搜索订单")
  285. self.search_btn.clicked.connect(self.search_orders)
  286. self.search_btn.setFixedHeight(35)
  287. btn_layout.addWidget(self.search_btn)
  288. self.refresh_btn = QPushButton("🔄 刷新")
  289. self.refresh_btn.clicked.connect(self.refresh_orders)
  290. self.refresh_btn.setFixedHeight(35)
  291. btn_layout.addWidget(self.refresh_btn)
  292. btn_layout.addStretch()
  293. config_layout.addLayout(btn_layout, 2, 0, 1, 3)
  294. config_group.setLayout(config_layout)
  295. main_layout.addWidget(config_group)
  296. # --- 订单列表 ---
  297. list_group = QGroupBox("订单列表")
  298. list_layout = QVBoxLayout()
  299. # 表格
  300. self.table = QTableWidget()
  301. self.table.setColumnCount(7)
  302. self.table.setHorizontalHeaderLabels(["序号", "订单号", "商品信息", "IMEI", "预估价格", "门店", "充电次数"])
  303. self.table.setColumnWidth(0, 50)
  304. self.table.setColumnWidth(1, 200)
  305. self.table.setColumnWidth(2, 250)
  306. self.table.setColumnWidth(3, 150)
  307. self.table.setColumnWidth(4, 100)
  308. self.table.setColumnWidth(5, 200)
  309. self.table.setColumnWidth(6, 100)
  310. self.table.horizontalHeader().setStretchLastSection(True)
  311. self.table.verticalHeader().setVisible(False)
  312. list_layout.addWidget(self.table)
  313. list_group.setLayout(list_layout)
  314. main_layout.addWidget(list_group)
  315. # --- 日志区域 ---
  316. log_group = QGroupBox("日志")
  317. log_layout = QVBoxLayout()
  318. # 日志文本框
  319. self.log_text = QTextEdit()
  320. self.log_text.setObjectName("log_text")
  321. self.log_text.setReadOnly(True)
  322. self.log_text.setMaximumHeight(200)
  323. log_layout.addWidget(self.log_text)
  324. # 日志控制按钮
  325. log_btn_layout = QHBoxLayout()
  326. self.clear_log_btn = QPushButton("清空日志")
  327. self.clear_log_btn.clicked.connect(self.clear_log)
  328. log_btn_layout.addWidget(self.clear_log_btn)
  329. log_btn_layout.addStretch()
  330. log_layout.addLayout(log_btn_layout)
  331. log_group.setLayout(log_layout)
  332. main_layout.addWidget(log_group)
  333. # --- 底部状态栏 ---
  334. self.status_bar = QStatusBar()
  335. self.setStatusBar(self.status_bar)
  336. self.status_bar.showMessage("就绪")
  337. def toggle_token_visibility(self):
  338. """切换Token显示/隐藏"""
  339. if self.token_input.echoMode() == QLineEdit.Password:
  340. self.token_input.setEchoMode(QLineEdit.Normal)
  341. self.show_token_btn.setText("隐藏")
  342. else:
  343. self.token_input.setEchoMode(QLineEdit.Password)
  344. self.show_token_btn.setText("显示")
  345. def log_message(self, message, level="INFO"):
  346. """添加日志消息"""
  347. timestamp = QDateTime.currentDateTime().toString("yyyy-MM-dd hh:mm:ss")
  348. color_map = {
  349. "INFO": "#d4d4d4",
  350. "WARNING": "#ffa500",
  351. "ERROR": "#ff4444",
  352. "SUCCESS": "#4caf50"
  353. }
  354. color = color_map.get(level, "#d4d4d4")
  355. html = f'<span style="color: #888888;">[{timestamp}]</span> <span style="color: {color};">[{level}]</span> {message}<br>'
  356. self.log_text.append(html)
  357. # 滚动到底部
  358. scrollbar = self.log_text.verticalScrollBar()
  359. scrollbar.setValue(scrollbar.maximum())
  360. def clear_log(self):
  361. """清空日志"""
  362. self.log_text.clear()
  363. def search_orders(self):
  364. """搜索订单"""
  365. access_token = self.token_input.text().strip()
  366. ocr_url = self.ocr_input.text().strip()
  367. if not access_token:
  368. QMessageBox.warning(self, "提示", "请输入Access Token")
  369. return
  370. if not ocr_url:
  371. QMessageBox.warning(self, "提示", "请输入OCR地址")
  372. return
  373. self.log_message("开始搜索订单...", "INFO")
  374. # 初始化订单管理器
  375. self.order_manager = OrderManager(access_token, ocr_url)
  376. # 清空缓存
  377. self.order_details_cache = {}
  378. self.charge_counts = {}
  379. self.status_bar.showMessage("正在获取订单列表...")
  380. self.search_btn.setEnabled(False)
  381. # 在后台线程执行
  382. self.worker = OrderListWorker(self.order_manager)
  383. self.worker.finished.connect(self.on_orders_loaded)
  384. self.worker.error.connect(self.on_error)
  385. self.worker.start()
  386. def refresh_orders(self):
  387. """刷新订单列表"""
  388. if self.order_manager:
  389. self.search_orders()
  390. else:
  391. QMessageBox.warning(self, "提示", "请先搜索订单")
  392. def on_orders_loaded(self, order_list):
  393. """订单列表加载完成"""
  394. self.order_list = order_list
  395. self.display_orders()
  396. self.search_btn.setEnabled(True)
  397. self.status_bar.showMessage(f"成功加载 {len(order_list)} 条订单")
  398. self.log_message(f"成功加载 {len(order_list)} 条订单", "SUCCESS")
  399. def on_error(self, error_msg):
  400. """错误处理"""
  401. self.search_btn.setEnabled(True)
  402. self.status_bar.showMessage("加载失败")
  403. self.log_message(f"获取订单列表失败: {error_msg}", "ERROR")
  404. QMessageBox.critical(self, "错误", f"获取订单列表失败:\n{error_msg}")
  405. def display_orders(self):
  406. """显示订单列表"""
  407. self.table.setRowCount(len(self.order_list))
  408. for i, order in enumerate(self.order_list):
  409. # 序号
  410. self.table.setItem(i, 0, QTableWidgetItem(str(i + 1)))
  411. # 订单号
  412. order_no = order.get('orderNo', '')
  413. self.table.setItem(i, 1, QTableWidgetItem(order_no))
  414. # 商品信息
  415. brand_info = order.get('brandInfo', '')
  416. self.table.setItem(i, 2, QTableWidgetItem(brand_info))
  417. # IMEI
  418. imei = order.get('imei', '')
  419. self.table.setItem(i, 3, QTableWidgetItem(imei))
  420. # 预估价格
  421. price = order.get('price', '')
  422. price_str = str(price) if price else '待定'
  423. self.table.setItem(i, 4, QTableWidgetItem(price_str))
  424. # 门店
  425. title = order.get('title', '')
  426. self.table.setItem(i, 5, QTableWidgetItem(title))
  427. # 充电次数(初始为空,显示"查询"按钮)
  428. charge_widget = QWidget()
  429. charge_layout = QHBoxLayout(charge_widget)
  430. charge_layout.setContentsMargins(5, 2, 5, 2)
  431. charge_layout.setSpacing(5)
  432. charge_label = QLabel("未查询")
  433. charge_label.setObjectName(f"charge_label_{i}")
  434. charge_layout.addWidget(charge_label)
  435. check_btn = QPushButton("查询")
  436. check_btn.setFixedSize(50, 25)
  437. check_btn.setObjectName(f"check_btn_{i}")
  438. check_btn.clicked.connect(lambda checked, idx=i: self.check_charge_count(idx))
  439. charge_layout.addWidget(check_btn)
  440. charge_widget.setLayout(charge_layout)
  441. self.table.setCellWidget(i, 6, charge_widget)
  442. def check_charge_count(self, index):
  443. """查询充电次数"""
  444. if not self.order_manager:
  445. return
  446. if index >= len(self.order_list):
  447. return
  448. order_no = self.order_list[index].get('orderNo')
  449. if not order_no:
  450. return
  451. self.log_message(f"开始查询订单 {order_no} 的充电次数", "INFO")
  452. # 检查是否已缓存
  453. if order_no in self.charge_counts:
  454. count = self.charge_counts[order_no]
  455. self.update_charge_display(index, count)
  456. self.log_message(f"订单 {order_no} 充电次数: {count} (来自缓存)", "INFO")
  457. return
  458. self.status_bar.showMessage(f"正在查询订单 {order_no} 的充电次数...")
  459. # 禁用按钮
  460. btn = self.findChild(QPushButton, f"check_btn_{index}")
  461. if btn:
  462. btn.setEnabled(False)
  463. btn.setText("查询中")
  464. # 在后台线程执行
  465. self.charge_worker = ChargeQueryWorker(self.order_manager, order_no, index)
  466. self.charge_worker.finished.connect(self.on_charge_queried)
  467. self.charge_worker.error.connect(self.on_charge_error)
  468. self.charge_worker.start()
  469. def on_charge_queried(self, index, charge_count, ocr_result, image_notices):
  470. """充电次数查询完成"""
  471. order_no = self.order_list[index].get('orderNo')
  472. # 如果没有找到充电次数,输出详细信息
  473. if charge_count is None:
  474. if ocr_result:
  475. self.log_message(f"订单 {order_no} 未找到充电次数,OCR识别结果:", "WARNING")
  476. ocr_text = self.order_manager.extract_text_from_ocr_result(ocr_result)
  477. if ocr_text:
  478. self.log_message(f"OCR文本: {ocr_text[:200]}...", "INFO")
  479. else:
  480. self.log_message(f"OCR文本为空", "WARNING")
  481. else:
  482. self.log_message(f"订单 {order_no} OCR识别失败", "ERROR")
  483. if image_notices:
  484. self.log_message(f"订单 {order_no} 找到的图片notice: {', '.join(image_notices)}", "INFO")
  485. else:
  486. self.log_message(f"订单 {order_no} 未找到验机图片", "ERROR")
  487. else:
  488. self.log_message(f"订单 {order_no} 充电次数: {charge_count}", "SUCCESS")
  489. self.charge_counts[order_no] = charge_count
  490. self.update_charge_display(index, charge_count)
  491. btn = self.findChild(QPushButton, f"check_btn_{index}")
  492. if btn:
  493. btn.setEnabled(True)
  494. btn.setText("查询")
  495. if charge_count is not None:
  496. self.status_bar.showMessage(f"订单 {order_no} 充电次数: {charge_count}")
  497. else:
  498. self.status_bar.showMessage(f"订单 {order_no} 未找到充电次数信息")
  499. def on_charge_error(self, index, error_msg):
  500. """充电次数查询错误"""
  501. order_no = self.order_list[index].get('orderNo')
  502. self.log_message(f"订单 {order_no} 查询失败: {error_msg}", "ERROR")
  503. btn = self.findChild(QPushButton, f"check_btn_{index}")
  504. if btn:
  505. btn.setEnabled(True)
  506. btn.setText("查询")
  507. self.status_bar.showMessage(f"查询失败: {error_msg}")
  508. QMessageBox.warning(self, "查询失败", f"获取订单详情失败:\n{error_msg}")
  509. def update_charge_display(self, index, charge_count):
  510. """更新充电次数显示"""
  511. widget = self.table.cellWidget(index, 6)
  512. if widget:
  513. label = widget.findChild(QLabel)
  514. if label:
  515. if charge_count is not None:
  516. label.setText(f"{charge_count} 次")
  517. label.setStyleSheet("color: #28a745; font-weight: bold;")
  518. else:
  519. label.setText("未找到")
  520. label.setStyleSheet("color: #dc3545;")
  521. # --- 后台工作线程 ---
  522. class OrderListWorker(QThread):
  523. finished = pyqtSignal(list)
  524. error = pyqtSignal(str)
  525. def __init__(self, order_manager):
  526. super().__init__()
  527. self.order_manager = order_manager
  528. def run(self):
  529. try:
  530. order_list, error = self.order_manager.get_pending_order_list()
  531. if error:
  532. self.error.emit(error)
  533. else:
  534. self.finished.emit(order_list)
  535. except Exception as e:
  536. self.error.emit(str(e))
  537. class ChargeQueryWorker(QThread):
  538. finished = pyqtSignal(int, object, object, list) # index, charge_count, ocr_result, image_notices
  539. error = pyqtSignal(int, str)
  540. def __init__(self, order_manager, order_no, index):
  541. super().__init__()
  542. self.order_manager = order_manager
  543. self.order_no = order_no
  544. self.index = index
  545. def run(self):
  546. try:
  547. # 获取订单详情
  548. order_detail, error = self.order_manager.get_order_detail(self.order_no)
  549. if error:
  550. self.error.emit(self.index, error)
  551. return
  552. # 查找所有图片的notice
  553. image_notices = self.order_manager.get_all_image_notices(order_detail)
  554. # 查找验机图片
  555. report_img = self.order_manager.find_original_image_by_notice(order_detail, "苹果沙漏验机报告")
  556. if report_img is None:
  557. report_img = self.order_manager.find_original_image_by_notice(order_detail, "苹果沙漏验机基本信息")
  558. if report_img is None:
  559. report_img = self.order_manager.find_original_image_by_notice(order_detail, "验机照片")
  560. if report_img is None:
  561. report_img = self.order_manager.find_original_image_by_notice(order_detail, "苹果验机软件")
  562. if not report_img:
  563. # 没有找到图片,返回notice列表
  564. self.finished.emit(self.index, None, None, image_notices)
  565. return
  566. # OCR识别
  567. ocr_result = self.order_manager.download_and_ocr_image(report_img)
  568. if not ocr_result:
  569. # OCR失败,返回notice列表
  570. self.finished.emit(self.index, None, None, image_notices)
  571. return
  572. # 提取充电次数
  573. ocr_text = self.order_manager.extract_text_from_ocr_result(ocr_result)
  574. charge_count = self.order_manager.extract_charge_count(ocr_text)
  575. self.finished.emit(self.index, charge_count, ocr_result, image_notices)
  576. except Exception as e:
  577. self.error.emit(self.index, str(e))
  578. # --- 主程序入口 ---
  579. def main():
  580. app = QApplication(sys.argv)
  581. app.setStyle('Fusion')
  582. # 设置应用图标
  583. app.setWindowIcon(QIcon())
  584. window = MainWindow()
  585. window.show()
  586. sys.exit(app.exec_())
  587. if __name__ == "__main__":
  588. main()