爱回收.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756
  1. import tkinter as tk
  2. from tkinter import ttk, messagebox
  3. import json
  4. import requests
  5. import threading
  6. from datetime import datetime
  7. import uuid
  8. import time
  9. import os
  10. import configparser
  11. class PhoneQueryApp:
  12. def __init__(self, root):
  13. self.root = root
  14. self.root.title("手机数据查询系统")
  15. self.root.geometry("1300x850")
  16. # 配置文件
  17. self.config_file = "config.ini"
  18. self.token = self.load_token()
  19. # 商品数据
  20. self.current_products = []
  21. self.is_loading_cycles = False
  22. self.current_page = 0
  23. self.total_pages = 0
  24. self.page_size = 30
  25. self.current_keyword = ""
  26. self.current_sort = "sort_composite" # 默认综合排序
  27. # 排序选项映射
  28. self.sort_map = {
  29. "综合排序": "sort_composite",
  30. "价格从低到高": "sort_price_asc",
  31. "价格从高到低": "sort_price_desc"
  32. }
  33. # 筛选选项映射
  34. self.memory_map = {
  35. "1T": 187,
  36. "512G": 192,
  37. "256G": 209,
  38. "128G": 210,
  39. "64G": 217,
  40. }
  41. self.battery_map = {
  42. "100%": 1680,
  43. "95%-99%": 5699,
  44. "90%-95%": 5700,
  45. "85%-90%": 5697,
  46. "80%-85%": 5698,
  47. "70%-80%": 6159,
  48. "70%以下": 6160,
  49. "不支持": 1627
  50. }
  51. # 状况标签映射
  52. self.condition_tags = {
  53. 10: "屏幕完美",
  54. 11: "机身无痕",
  55. 12: "功能完好",
  56. 17: "电池效率95%+",
  57. 20: "原厂在保",
  58. 19: "无维修",
  59. 15: "官方在保",
  60. 13: "原厂电池",
  61. 14: "原厂屏幕",
  62. 21: "原厂部件"
  63. }
  64. self.create_widgets()
  65. self.result_tree.bind("<Button-3>", self.show_context_menu)
  66. def load_token(self):
  67. """从配置文件加载Token"""
  68. if os.path.exists(self.config_file):
  69. try:
  70. config = configparser.ConfigParser()
  71. config.read(self.config_file)
  72. return config.get('Settings', 'token', fallback='dccf18b052ef46d78d4feb796182d5df')
  73. except:
  74. return 'dccf18b052ef46d78d4feb796182d5df'
  75. return 'dccf18b052ef46d78d4feb796182d5df'
  76. def save_token(self):
  77. """保存Token到配置文件"""
  78. try:
  79. config = configparser.ConfigParser()
  80. if os.path.exists(self.config_file):
  81. config.read(self.config_file)
  82. if not config.has_section('Settings'):
  83. config.add_section('Settings')
  84. config.set('Settings', 'token', self.token_var.get())
  85. with open(self.config_file, 'w') as f:
  86. config.write(f)
  87. self.token = self.token_var.get()
  88. messagebox.showinfo("成功", "Token已保存")
  89. return True
  90. except Exception as e:
  91. messagebox.showerror("错误", f"保存Token失败: {e}")
  92. return False
  93. def get_headers(self):
  94. """获取请求头"""
  95. return {
  96. "Connection": "keep-alive",
  97. "content-type": "application/json;charset=UTF-8",
  98. "ahs-app-version": "7.49.3",
  99. "Ahs-Timestamp": str(int(datetime.now().timestamp())),
  100. "Ahs-Token": self.token,
  101. "Ahs-Session-Id": str(uuid.uuid4()),
  102. "Ahs-App-Id": "10007",
  103. "Ahs-Device-Id": "3166e003-4ba7-4b57-8158-30859d81c7d5",
  104. "Ahs-Sign": "9ea25fcfcda37585065244ed271f1b0d",
  105. "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 26_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.74(0x18004a2c) NetType/WIFI Language/zh_CN",
  106. "Referer": "https://servicewechat.com/wx7e490492b4c23e98/1055/page-frame.html",
  107. "Accept": "*/*",
  108. "Accept-Encoding": "gzip, deflate, br"
  109. }
  110. def create_widgets(self):
  111. """创建界面组件"""
  112. main_frame = ttk.Frame(self.root, padding="10")
  113. main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
  114. self.root.columnconfigure(0, weight=1)
  115. self.root.rowconfigure(0, weight=1)
  116. main_frame.columnconfigure(0, weight=1)
  117. main_frame.rowconfigure(5, weight=1)
  118. # Token设置区域
  119. token_frame = ttk.LabelFrame(main_frame, text="Token设置", padding="10")
  120. token_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 10))
  121. token_frame.columnconfigure(1, weight=1)
  122. ttk.Label(token_frame, text="Ahs-Token:", font=("Arial", 10)).grid(row=0, column=0, sticky=tk.W, padx=(0, 10))
  123. self.token_var = tk.StringVar(value=self.token)
  124. self.token_entry = ttk.Entry(token_frame, textvariable=self.token_var, width=60, font=("Arial", 10))
  125. self.token_entry.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(0, 10))
  126. self.save_token_btn = ttk.Button(token_frame, text="保存Token", command=self.save_token, width=12)
  127. self.save_token_btn.grid(row=0, column=2)
  128. # 查询区域
  129. query_frame = ttk.LabelFrame(main_frame, text="查询条件", padding="10")
  130. query_frame.grid(row=1, column=0, sticky=(tk.W, tk.E), pady=(0, 10))
  131. query_frame.columnconfigure(1, weight=1)
  132. query_frame.columnconfigure(3, weight=1)
  133. # 机型输入
  134. ttk.Label(query_frame, text="机型:", font=("Arial", 11)).grid(row=0, column=0, sticky=tk.W, padx=(0, 10))
  135. self.model_entry = ttk.Entry(query_frame, width=35, font=("Arial", 11))
  136. self.model_entry.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(0, 10))
  137. self.model_entry.insert(0, "iPhone 15 Pro")
  138. # 排序选择
  139. ttk.Label(query_frame, text="排序方式:", font=("Arial", 11)).grid(row=0, column=2, sticky=tk.W, padx=(10, 10))
  140. self.sort_var = tk.StringVar(value="综合排序")
  141. self.sort_combo = ttk.Combobox(query_frame, textvariable=self.sort_var,
  142. values=["综合排序", "价格从低到高", "价格从高到低", "最新上架", "人气最高"],
  143. width=15, state="readonly", font=("Arial", 10))
  144. self.sort_combo.grid(row=0, column=3, sticky=(tk.W, tk.E), padx=(0, 10))
  145. self.sort_combo.bind('<<ComboboxSelected>>', self.on_sort_changed)
  146. # 搜索按钮
  147. self.search_btn = ttk.Button(query_frame, text="搜索", command=self.search_products, width=15)
  148. self.search_btn.grid(row=0, column=4)
  149. # ==================== 筛选区域 ====================
  150. # 第一行:内存 + 电池效率(各占一半)
  151. filter_row1 = ttk.Frame(main_frame)
  152. filter_row1.grid(row=2, column=0, sticky=(tk.W, tk.E), pady=(0, 5))
  153. filter_row1.columnconfigure(0, weight=1)
  154. filter_row1.columnconfigure(1, weight=1)
  155. # 内存筛选
  156. memory_frame = ttk.LabelFrame(filter_row1, text="内存(多选)", padding="5")
  157. memory_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), padx=(0, 5))
  158. self.memory_vars = {}
  159. memory_options = ["1T", "512G", "256G", "128G"]
  160. for i, option in enumerate(memory_options):
  161. var = tk.BooleanVar()
  162. self.memory_vars[option] = var
  163. cb = ttk.Checkbutton(memory_frame, text=option, variable=var)
  164. cb.grid(row=0, column=i, sticky=tk.W, padx=15, pady=3)
  165. # 电池效率筛选
  166. battery_frame = ttk.LabelFrame(filter_row1, text="电池效率(多选)", padding="5")
  167. battery_frame.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(5, 0))
  168. self.battery_vars = {}
  169. battery_options = ["100%", "95%-99%", "90%-95%", "85%-90%", "80%-85%", "70%-80%", "70%以下", "不支持"]
  170. for i, option in enumerate(battery_options):
  171. var = tk.BooleanVar()
  172. self.battery_vars[option] = var
  173. row = i // 4
  174. col = i % 4
  175. cb = ttk.Checkbutton(battery_frame, text=option, variable=var)
  176. cb.grid(row=row, column=col, sticky=tk.W, padx=10, pady=3)
  177. # 第二行:成色 + 状况标签(各占一半)
  178. filter_row2 = ttk.Frame(main_frame)
  179. filter_row2.grid(row=3, column=0, sticky=(tk.W, tk.E), pady=(0, 5))
  180. filter_row2.columnconfigure(0, weight=1)
  181. filter_row2.columnconfigure(1, weight=1)
  182. # 成色筛选
  183. fineness_frame = ttk.LabelFrame(filter_row2, text="成色(多选)", padding="5")
  184. fineness_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), padx=(0, 5))
  185. self.fineness_vars = {}
  186. fineness_options = ["全新仅拆封", "准新机", "99新", "95新", "9成新", "8成新", "7成新"]
  187. for i, option in enumerate(fineness_options):
  188. var = tk.BooleanVar()
  189. self.fineness_vars[option] = var
  190. cb = ttk.Checkbutton(fineness_frame, text=option, variable=var)
  191. cb.grid(row=0, column=i, sticky=tk.W, padx=10, pady=3)
  192. # 状况标签筛选
  193. condition_frame = ttk.LabelFrame(filter_row2, text="状况标签(多选)", padding="5")
  194. condition_frame.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(5, 0))
  195. self.condition_vars = {}
  196. condition_items = list(self.condition_tags.items())
  197. for i, (tag_id, tag_name) in enumerate(condition_items):
  198. var = tk.BooleanVar()
  199. self.condition_vars[tag_id] = var
  200. row = i // 5
  201. col = i % 5
  202. cb = ttk.Checkbutton(condition_frame, text=tag_name, variable=var)
  203. cb.grid(row=row, column=col, sticky=tk.W, padx=10, pady=3)
  204. # ==================== 结果显示区域 ====================
  205. result_frame = ttk.LabelFrame(main_frame, text="查询结果(可多选商品,点击批量收藏)", padding="10")
  206. result_frame.grid(row=4, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
  207. result_frame.columnconfigure(0, weight=1)
  208. result_frame.rowconfigure(0, weight=1)
  209. # 添加电池效率列
  210. columns = ("名称", "价格", "电池效率", "循环次数", "成色", "内存", "颜色", "网络制式")
  211. self.result_tree = ttk.Treeview(result_frame, columns=columns, show="headings", height=15, selectmode="extended")
  212. self.result_tree.heading("名称", text="名称")
  213. self.result_tree.heading("价格", text="价格")
  214. self.result_tree.heading("电池效率", text="电池效率")
  215. self.result_tree.heading("循环次数", text="循环次数")
  216. self.result_tree.heading("成色", text="成色")
  217. self.result_tree.heading("内存", text="内存")
  218. self.result_tree.heading("颜色", text="颜色")
  219. self.result_tree.heading("网络制式", text="网络制式")
  220. self.result_tree.column("名称", width=350)
  221. self.result_tree.column("价格", width=90)
  222. self.result_tree.column("电池效率", width=100)
  223. self.result_tree.column("循环次数", width=90)
  224. self.result_tree.column("成色", width=90)
  225. self.result_tree.column("内存", width=70)
  226. self.result_tree.column("颜色", width=90)
  227. self.result_tree.column("网络制式", width=90)
  228. scrollbar = ttk.Scrollbar(result_frame, orient=tk.VERTICAL, command=self.result_tree.yview)
  229. self.result_tree.configure(yscrollcommand=scrollbar.set)
  230. self.result_tree.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
  231. scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
  232. # ==================== 底部按钮栏 ====================
  233. bottom_frame = ttk.Frame(main_frame)
  234. bottom_frame.grid(row=5, column=0, sticky=(tk.W, tk.E), pady=(10, 5))
  235. self.check_price_btn = ttk.Button(bottom_frame, text="获取循环次数", command=self.check_prices, width=18)
  236. self.check_price_btn.pack(side=tk.LEFT, padx=(0, 10))
  237. self.batch_collect_btn = ttk.Button(bottom_frame, text="批量收藏所选商品", command=self.batch_collect, width=20)
  238. self.batch_collect_btn.pack(side=tk.LEFT, padx=(0, 20))
  239. self.progress_var = tk.StringVar()
  240. self.progress_var.set("")
  241. ttk.Label(bottom_frame, textvariable=self.progress_var, font=("Arial", 9)).pack(side=tk.LEFT, padx=(20, 0))
  242. # ==================== 分页栏 ====================
  243. pagination_frame = ttk.Frame(main_frame)
  244. pagination_frame.grid(row=6, column=0, sticky=(tk.W, tk.E), pady=(0, 5))
  245. self.prev_btn = ttk.Button(pagination_frame, text="上一页", command=self.prev_page, width=10)
  246. self.prev_btn.pack(side=tk.LEFT, padx=(0, 10))
  247. self.next_btn = ttk.Button(pagination_frame, text="下一页", command=self.next_page, width=10)
  248. self.next_btn.pack(side=tk.LEFT, padx=(0, 20))
  249. ttk.Label(pagination_frame, text="第").pack(side=tk.LEFT)
  250. self.page_entry = ttk.Entry(pagination_frame, width=8)
  251. self.page_entry.pack(side=tk.LEFT, padx=(5, 5))
  252. ttk.Label(pagination_frame, text="页").pack(side=tk.LEFT)
  253. self.jump_btn = ttk.Button(pagination_frame, text="跳转", command=self.jump_to_page, width=8)
  254. self.jump_btn.pack(side=tk.LEFT, padx=(10, 20))
  255. self.page_info_var = tk.StringVar()
  256. self.page_info_var.set("第 0 / 0 页")
  257. page_label = ttk.Label(pagination_frame, textvariable=self.page_info_var, font=("Arial", 9))
  258. page_label.pack(side=tk.LEFT)
  259. # ==================== 状态栏 ====================
  260. status_frame = ttk.Frame(main_frame)
  261. status_frame.grid(row=7, column=0, sticky=(tk.W, tk.E), pady=(5, 0))
  262. self.status_var = tk.StringVar()
  263. self.status_var.set("就绪")
  264. status_bar = ttk.Label(status_frame, textvariable=self.status_var, relief=tk.SUNKEN)
  265. status_bar.pack(side=tk.LEFT, fill=tk.X, expand=True)
  266. self.update_pagination_buttons()
  267. def on_sort_changed(self, event=None):
  268. """排序方式改变时自动搜索"""
  269. if self.model_entry.get().strip():
  270. self.search_products()
  271. def get_selected_memory(self):
  272. """获取选中的内存PPV ID列表"""
  273. selected = []
  274. for option, var in self.memory_vars.items():
  275. if var.get():
  276. selected.append(self.memory_map[option])
  277. return selected
  278. def get_selected_battery(self):
  279. """获取选中的电池效率PPV ID列表"""
  280. selected = []
  281. for option, var in self.battery_vars.items():
  282. if var.get():
  283. selected.append(self.battery_map[option])
  284. return selected
  285. def get_selected_fineness(self):
  286. fineness_map = {
  287. "全新仅拆封": 111,
  288. "准新机": 100,
  289. "99新": 99,
  290. "95新": 95,
  291. "9成新": 90,
  292. "8成新": 80,
  293. "7成新": 70
  294. }
  295. selected = []
  296. for option, var in self.fineness_vars.items():
  297. if var.get():
  298. selected.append(fineness_map[option])
  299. return selected
  300. def get_selected_condition_tags(self):
  301. selected = []
  302. for tag_id, var in self.condition_vars.items():
  303. if var.get():
  304. selected.append(tag_id)
  305. return selected
  306. def get_battery_efficiency(self, condition_labels):
  307. """从conditionLabels中提取电池效率"""
  308. if not condition_labels:
  309. return ""
  310. for label in condition_labels:
  311. if label.get('group') in ['原厂电池', '第三方电池']:
  312. return label.get('label', '')
  313. return ""
  314. def search_products(self, page=0):
  315. keyword = self.model_entry.get().strip()
  316. if not keyword:
  317. messagebox.showwarning("警告", "请输入机型")
  318. return
  319. self.current_keyword = keyword
  320. self.current_page = page
  321. # 获取当前选中的排序方式
  322. self.current_sort = self.sort_map.get(self.sort_var.get(), "sort_composite")
  323. for item in self.result_tree.get_children():
  324. self.result_tree.delete(item)
  325. self.current_products.clear()
  326. self.progress_var.set("")
  327. self.check_price_btn.config(state="normal")
  328. self.batch_collect_btn.config(state="normal")
  329. self.status_var.set("正在搜索...")
  330. self.search_btn.config(state="disabled")
  331. self.prev_btn.config(state="disabled")
  332. self.next_btn.config(state="disabled")
  333. self.jump_btn.config(state="disabled")
  334. thread = threading.Thread(target=self.do_search, args=(keyword, page))
  335. thread.daemon = True
  336. thread.start()
  337. def do_search(self, keyword, page):
  338. try:
  339. # 获取筛选条件
  340. memory_ids = self.get_selected_memory()
  341. battery_ids = self.get_selected_battery()
  342. fineness_ids = self.get_selected_fineness()
  343. condition_tags = self.get_selected_condition_tags()
  344. request_data = {
  345. "sortValue": self.current_sort, # 使用当前选中的排序方式
  346. "scene": "search",
  347. "keyword": keyword,
  348. "sirReq": False,
  349. "cityId": 324,
  350. "pageIndex": page,
  351. "pageSize": self.page_size
  352. }
  353. # 添加内存筛选(使用 gaeaSkuPpvIds)
  354. if memory_ids:
  355. request_data["gaeaSkuPpvIds"] = {
  356. "22": memory_ids # 22是内存的ppnId
  357. }
  358. # 添加电池效率筛选(使用 gaeaPricePpvIds)
  359. if battery_ids:
  360. request_data["gaeaPricePpvIds"] = {
  361. "473": battery_ids # 473是电池效率的ppnId
  362. }
  363. # 添加成色筛选
  364. if fineness_ids:
  365. request_data["gaeaFinenessIds"] = fineness_ids
  366. # 添加状况标签筛选
  367. if condition_tags:
  368. request_data["conditionTagList"] = condition_tags
  369. url = "https://dubai-mp.aihuishou.com/ahs-yanxuan-service/products/search-goods-v2"
  370. response = requests.post(url, json=request_data, headers=self.get_headers(), timeout=30)
  371. result = response.json()
  372. print("请求参数:", json.dumps(request_data, ensure_ascii=False))
  373. self.root.after(0, self.display_results, result, page)
  374. except Exception as e:
  375. self.root.after(0, self.show_error, str(e))
  376. def parse_product_info(self, product):
  377. name = product.get('name', '')
  378. sale_goods_no = product.get('saleGoodsNo', '')
  379. product_no = product.get('productNo', '')
  380. # 提取颜色
  381. color = ""
  382. color_keywords = ['黑色', '白色', '蓝色', '原色', '金色', '银色', '石墨色', '紫色', '绿色', '粉色', '红色', '黄色', '暗紫色', '深空黑色']
  383. for keyword in color_keywords:
  384. if keyword in name:
  385. color = keyword
  386. break
  387. # 获取电池效率
  388. condition_labels = product.get('conditionLabels', [])
  389. battery_efficiency = self.get_battery_efficiency(condition_labels)
  390. # 如果conditionLabels中没有,从productTag中获取
  391. if not battery_efficiency:
  392. product_tags = product.get('productTag', [])
  393. for tag in product_tags:
  394. if '电池' in tag:
  395. battery_efficiency = tag
  396. break
  397. return {
  398. 'name': name[:50],
  399. 'price': product.get('price', 0),
  400. 'battery_efficiency': battery_efficiency,
  401. 'cycle_count': '',
  402. 'fineness': product.get('gaeaFinenessName', ''),
  403. 'memory': product.get('memoryDesc', ''),
  404. 'color': color,
  405. 'network': "全网通",
  406. 'saleGoodsNo': sale_goods_no,
  407. 'productNo': product_no
  408. }
  409. def display_results(self, result, page):
  410. self.search_btn.config(state="normal")
  411. self.jump_btn.config(state="normal")
  412. if result.get('code') != 0:
  413. self.status_var.set(f"查询失败: {result.get('resultMessage', '未知错误')}")
  414. messagebox.showerror("错误", result.get('resultMessage', '查询失败'))
  415. return
  416. data = result.get('data', [])
  417. total_count = result.get('totalCount', 0)
  418. if total_count > 0:
  419. self.total_pages = (total_count + self.page_size - 1) // self.page_size
  420. else:
  421. if len(data) < self.page_size:
  422. self.total_pages = page + 1
  423. else:
  424. self.total_pages = page + 2
  425. if not data:
  426. self.status_var.set("未找到相关产品")
  427. messagebox.showinfo("提示", "未找到相关产品")
  428. self.update_pagination_buttons()
  429. return
  430. for product in data:
  431. info = self.parse_product_info(product)
  432. item_id = self.result_tree.insert("", tk.END, values=(
  433. info['name'],
  434. f"¥{info['price']}",
  435. info['battery_efficiency'],
  436. info['cycle_count'],
  437. info['fineness'],
  438. info['memory'],
  439. info['color'],
  440. info['network']
  441. ))
  442. self.current_products.append({
  443. 'item_id': item_id,
  444. 'saleGoodsNo': info['saleGoodsNo'],
  445. 'productNo': info['productNo'],
  446. 'info': info
  447. })
  448. sort_name = self.sort_var.get()
  449. self.status_var.set(f"第 {page + 1} 页,排序: {sort_name},共找到 {len(data)} 条结果")
  450. self.update_pagination_buttons()
  451. def update_pagination_buttons(self):
  452. self.page_info_var.set(f"第 {self.current_page + 1} / {self.total_pages if self.total_pages > 0 else 1} 页")
  453. if self.current_page <= 0:
  454. self.prev_btn.config(state="disabled")
  455. else:
  456. self.prev_btn.config(state="normal")
  457. self.next_btn.config(state="normal")
  458. if self.total_pages > 0 and self.current_page >= self.total_pages - 1:
  459. self.next_btn.config(state="disabled")
  460. else:
  461. if len(self.current_products) >= self.page_size:
  462. self.next_btn.config(state="normal")
  463. else:
  464. self.next_btn.config(state="disabled")
  465. def prev_page(self):
  466. if self.current_page > 0:
  467. self.search_products(self.current_page - 1)
  468. def next_page(self):
  469. if self.total_pages == 0 or self.current_page < self.total_pages - 1:
  470. self.search_products(self.current_page + 1)
  471. def jump_to_page(self):
  472. try:
  473. page_num = int(self.page_entry.get().strip())
  474. if page_num < 1:
  475. messagebox.showwarning("警告", "页码必须大于0")
  476. return
  477. if self.total_pages > 0 and page_num > self.total_pages:
  478. messagebox.showwarning("警告", f"页码不能超过 {self.total_pages}")
  479. return
  480. self.search_products(page_num - 1)
  481. self.page_entry.delete(0, tk.END)
  482. except ValueError:
  483. messagebox.showwarning("警告", "请输入有效的页码")
  484. def get_cycle_count(self, sale_goods_no):
  485. """获取商品的电池循环次数"""
  486. try:
  487. url = f"https://dubai-mp.aihuishou.com/ahs-yanxuan-service/products/goods-tag-param?saleGoodsNo={sale_goods_no}"
  488. response = requests.get(url, headers=self.get_headers(), timeout=10)
  489. result = response.json()
  490. if result.get('code') == 0 and 'data' in result:
  491. data = result['data']
  492. if 'machineConditionList' in data:
  493. for item in data['machineConditionList']:
  494. if item.get('name') == '充电次数':
  495. return item.get('value', '')
  496. if 'aggConditionTagParamList' in data:
  497. for group in data['aggConditionTagParamList']:
  498. if group.get('groupName') == '核心机况':
  499. for item in group.get('valueList', []):
  500. if item.get('name') == '充电次数':
  501. return item.get('value', '')
  502. return ''
  503. except Exception as e:
  504. print(f"获取循环次数失败: {e}")
  505. return ''
  506. def add_to_collection(self, item_no):
  507. try:
  508. url = "https://dubai-mp.aihuishou.com/ahs-yanxuan-service/collect/save"
  509. payload = json.dumps({"itemNo": item_no, "type": 1})
  510. headers = self.get_headers()
  511. response = requests.post(url, data=payload, headers=headers, timeout=10)
  512. result = response.json()
  513. if result.get('code') == 200 and result.get('data') == True:
  514. return True, "收藏成功"
  515. else:
  516. return False, result.get('resultMessage', '收藏失败')
  517. except Exception as e:
  518. return False, str(e)
  519. def batch_collect(self):
  520. selected_items = self.result_tree.selection()
  521. if not selected_items:
  522. messagebox.showwarning("警告", "请先选择要收藏的商品(可多选)")
  523. return
  524. result = messagebox.askyesno("确认收藏", f"确定要收藏选中的 {len(selected_items)} 个商品吗?")
  525. if not result:
  526. return
  527. thread = threading.Thread(target=self.process_batch_collect, args=(selected_items,))
  528. thread.daemon = True
  529. thread.start()
  530. def process_batch_collect(self, selected_items):
  531. self.batch_collect_btn.config(state="disabled")
  532. self.check_price_btn.config(state="disabled")
  533. success_count = 0
  534. fail_count = 0
  535. total = len(selected_items)
  536. for i, item_id in enumerate(selected_items):
  537. product = None
  538. for p in self.current_products:
  539. if p['item_id'] == item_id:
  540. product = p
  541. break
  542. if product:
  543. item_no = product['productNo'] or product['saleGoodsNo']
  544. self.root.after(0, lambda txt=f"收藏中: {i+1}/{total}": self.progress_var.set(txt))
  545. success, msg = self.add_to_collection(item_no)
  546. if success:
  547. success_count += 1
  548. else:
  549. fail_count += 1
  550. print(f"收藏失败: {product['info']['name']} - {msg}")
  551. time.sleep(0.2)
  552. self.root.after(0, lambda: self.finish_batch_collect(success_count, fail_count, total))
  553. def finish_batch_collect(self, success_count, fail_count, total):
  554. self.batch_collect_btn.config(state="normal")
  555. self.check_price_btn.config(state="normal")
  556. self.progress_var.set("")
  557. if fail_count == 0:
  558. self.status_var.set(f"批量收藏完成!成功收藏 {success_count} 个商品")
  559. messagebox.showinfo("批量收藏完成", f"成功收藏 {success_count} 个商品!")
  560. else:
  561. self.status_var.set(f"批量收藏完成:成功 {success_count},失败 {fail_count}")
  562. messagebox.showwarning("批量收藏完成", f"成功收藏 {success_count} 个商品\n失败 {fail_count} 个商品")
  563. def check_prices(self):
  564. if not self.current_products:
  565. messagebox.showwarning("警告", "请先搜索商品")
  566. return
  567. if self.is_loading_cycles:
  568. messagebox.showinfo("提示", "正在获取中,请稍候...")
  569. return
  570. thread = threading.Thread(target=self.process_check_prices)
  571. thread.daemon = True
  572. thread.start()
  573. def process_check_prices(self):
  574. self.is_loading_cycles = True
  575. self.check_price_btn.config(state="disabled")
  576. self.batch_collect_btn.config(state="disabled")
  577. total = len(self.current_products)
  578. for i, product in enumerate(self.current_products):
  579. self.root.after(0, lambda txt=f"获取中: {i+1}/{total}": self.progress_var.set(txt))
  580. cycle_count = self.get_cycle_count(product['saleGoodsNo'])
  581. if cycle_count:
  582. values = list(self.result_tree.item(product['item_id'], 'values'))
  583. values[3] = cycle_count
  584. self.root.after(0, lambda pid=product['item_id'], vals=values: self.result_tree.item(pid, values=vals))
  585. time.sleep(0.3)
  586. self.root.after(0, self.finish_check_prices)
  587. def finish_check_prices(self):
  588. self.is_loading_cycles = False
  589. self.check_price_btn.config(state="normal")
  590. self.batch_collect_btn.config(state="normal")
  591. self.progress_var.set("")
  592. self.status_var.set(f"完成!已获取 {len(self.current_products)} 条商品的循环次数")
  593. messagebox.showinfo("完成", f"已成功获取 {len(self.current_products)} 条商品的循环次数")
  594. def show_context_menu(self, event):
  595. item = self.result_tree.identify_row(event.y)
  596. if not item:
  597. return
  598. self.result_tree.selection_set(item)
  599. values = self.result_tree.item(item, 'values')
  600. product_name = values[0]
  601. battery = values[2]
  602. cycle_count = values[3]
  603. menu = tk.Menu(self.root, tearoff=0)
  604. menu.add_command(label=f"收藏: {product_name[:30]}", command=lambda: self.collect_single_item(item))
  605. if battery:
  606. menu.add_command(label=f"电池: {battery}", command=None, state="disabled")
  607. if cycle_count:
  608. menu.add_command(label=f"循环次数: {cycle_count}", command=None, state="disabled")
  609. menu.add_separator()
  610. menu.add_command(label="取消", command=None)
  611. menu.post(event.x_root, event.y_root)
  612. def collect_single_item(self, item):
  613. for product in self.current_products:
  614. if product['item_id'] == item:
  615. item_no = product['productNo'] or product['saleGoodsNo']
  616. success, msg = self.add_to_collection(item_no)
  617. if success:
  618. self.status_var.set(f"✓ 收藏成功: {product['info']['name'][:30]}")
  619. messagebox.showinfo("收藏成功", f"已成功收藏\n{product['info']['name']}")
  620. else:
  621. self.status_var.set(f"✗ 收藏失败: {msg}")
  622. messagebox.showerror("收藏失败", f"收藏失败\n{product['info']['name']}\n错误: {msg}")
  623. return
  624. def show_error(self, error_msg):
  625. self.search_btn.config(state="normal")
  626. self.jump_btn.config(state="normal")
  627. self.status_var.set(f"查询失败: {error_msg}")
  628. messagebox.showerror("错误", f"查询失败: {error_msg}")
  629. def main():
  630. root = tk.Tk()
  631. app = PhoneQueryApp(root)
  632. root.mainloop()
  633. if __name__ == "__main__":
  634. main()