ai客服
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1191 lines
56 KiB

1 month ago
  1. import time
  2. from playwright.sync_api import BrowserContext, Page
  3. def click_safe(target, selector, wait_for=None, timeout=5000):
  4. try:
  5. target.wait_for_selector(selector, state="visible", timeout=timeout)
  6. target.click(selector, force=True)
  7. time.sleep(1)
  8. if wait_for:
  9. target.wait_for_selector(wait_for, state="visible", timeout=timeout)
  10. return True
  11. except:
  12. return False
  13. def close_guides(page_or_frame):
  14. """
  15. ()
  16. """
  17. try:
  18. # 常见引导按钮文本和选择器
  19. guide_selectors = [
  20. "text=不再提示", "text=点击关闭", "text=我知道了", "text=下次再说",
  21. ".auxo-modal-close", ".arco-icon-close", ".auxo-guide-close",
  22. ".auxo-btn:has-text('不再提示')", ".arco-btn:has-text('不再提示')",
  23. "div[class*='guide'] .btn:has-text('关闭')"
  24. ]
  25. # 处理可能的多个引导
  26. for sel in guide_selectors:
  27. try:
  28. locators = page_or_frame.locator(sel)
  29. count = locators.count()
  30. for i in range(count):
  31. loc = locators.nth(i)
  32. if loc.is_visible():
  33. print(f" [清理引导] 正在关闭: {sel}")
  34. loc.click(force=True, timeout=1000)
  35. time.sleep(0.5)
  36. except:
  37. continue
  38. except:
  39. pass
  40. def find_work_context(page):
  41. """
  42. Iframe ()
  43. """
  44. # 特征点
  45. features = ["text=组合查询", "span.btn_search", ".btn-search", "text=等待订单合并"]
  46. # 1. 探测 Iframe (聚水潭业务通常嵌套在名为 epaas-iframe 或特定 ID 的 frame 中)
  47. for frame in page.frames:
  48. try:
  49. # 检查是否有业务特征
  50. for feat in features:
  51. if frame.locator(feat).count() > 0:
  52. print(f" [探测] 成功锁定业务 Frame: {frame.name or frame.url[:40]}...")
  53. return frame
  54. except:
  55. continue
  56. # 2. 检查主页面
  57. for feat in features:
  58. if page.locator(feat).count() > 0:
  59. return page
  60. return None
  61. def run_sync(browser_context: BrowserContext):
  62. print("\n[ERP自动化] 启动执行任务...")
  63. # 1. 查找或开启页面
  64. page = None
  65. for p in browser_context.pages:
  66. if "erp321.com" in p.url:
  67. page = p
  68. print(" -> 复用已有聚水潭页面...")
  69. break
  70. if not page:
  71. print(" -> 开启新标签页...")
  72. page = browser_context.new_page()
  73. should_close = True
  74. try:
  75. if "erp321.com" not in page.url or "login" in page.url:
  76. print(" -> 正在打开页面...")
  77. page.goto("https://www.erp321.com/epaas", timeout=60000)
  78. # 2. 清理首页可能的引导
  79. close_guides(page)
  80. # 3. 识别状态 (增强:如果已经在订单页 Iframe 里也能识别)
  81. print(" -> 正在识别页面状态...")
  82. work_canvas = find_work_context(page)
  83. if work_canvas:
  84. print(" ✅ 检测到已处于订单管理界面,跳过首页点击。")
  85. else:
  86. # 入口尝试路径:针对专业版进行优化
  87. entrance_selectors = [
  88. "a.menuItem___5lTDZ:has(p:text('订单'))",
  89. "p:text-is('订单')",
  90. "div[class*='menuItem']:has-text('订单')",
  91. "li:has-text('订单')",
  92. "text=订单"
  93. ]
  94. clicked = False
  95. # 在尝试点击前,先清理一次主页面的遮挡
  96. close_guides(page)
  97. for sel in entrance_selectors:
  98. try:
  99. loc = page.locator(sel).first
  100. if loc.count() > 0 and loc.is_visible():
  101. print(f" -> 步骤1: 触发测边栏入口: {sel}")
  102. loc.click(force=True, timeout=3000)
  103. time.sleep(1)
  104. # 步骤2: 聚水潭专业版通常需要二次点击浮层中的“订单”文字
  105. time.sleep(1.5)
  106. # 优先匹配面板内的文字
  107. secondary_selectors = [
  108. "div:text-is('订单')",
  109. "span:text-is('订单')",
  110. "a:has-text('订单')"
  111. ]
  112. for sec_sel in secondary_selectors:
  113. try:
  114. sec_loc = page.locator(sec_sel).filter(has_not=page.locator(sel)).first
  115. if sec_loc.count() > 0 and sec_loc.is_visible():
  116. print(f" -> 步骤2: 点击展开面板中的实体文字: {sec_sel}")
  117. sec_loc.click(force=True, timeout=2000)
  118. time.sleep(1)
  119. clicked = True
  120. break
  121. except:
  122. continue
  123. if clicked: break
  124. # 如果没有找到二级,可能一步就到位了
  125. clicked = True
  126. break
  127. except:
  128. continue
  129. if not clicked:
  130. # 最后的兜底:尝试点击侧边栏图标区域
  131. try:
  132. print(" -> 尝试兜底坐标点击侧边栏区域...")
  133. page.mouse.click(30, 100) # 通常侧边栏订单按钮在左上角区域
  134. clicked = True
  135. except:
  136. pass
  137. if not clicked:
  138. print(" ❌ 无法识别“订单”入口,请确保已登录并处于正常界面。")
  139. should_close = False
  140. return False
  141. print(" -> 已触发点击,等待工作台加载...")
  142. for i in range(5):
  143. time.sleep(3)
  144. work_canvas = find_work_context(page)
  145. if work_canvas: break
  146. print(f" (正在探测管理界面... 第 {i+1}/5 次)")
  147. if not work_canvas:
  148. print(" ⚠️ 未探测到工作环境,任务挂起。")
  149. should_close = False
  150. return False
  151. print(f" ✅ 已锁定工作台: {getattr(work_canvas, 'name', '订单子系统')}")
  152. # 进入工作台后的第一件事:清理工作台内的所有新手引导!
  153. close_guides(work_canvas)
  154. # 4. 执行业务逻辑 (必须在 work_canvas 中)
  155. # 清理工作台内的顶部干扰(如归档提醒等)
  156. try:
  157. ignore_bar = work_canvas.locator("#ArchiveOrderCloseBtn, .close-btn-tips").first
  158. if ignore_bar.count() > 0 and ignore_bar.is_visible():
  159. ignore_bar.click(force=True, timeout=2000)
  160. time.sleep(1)
  161. except: pass
  162. print(" -> 勾选'等待订单合并'...")
  163. is_checked = False
  164. try:
  165. # === 跨所有 frame 搜索 input#q_11 ===
  166. # work_canvas 可能不包含筛选面板,需要遍历所有 frame 找到真正包含 #q_11 的那个
  167. target_frame = None
  168. # 1. 先在 work_canvas 中找
  169. if work_canvas.locator("input#q_11").count() > 0:
  170. target_frame = work_canvas
  171. print(" -> 在 work_canvas 中找到 #q_11。")
  172. else:
  173. # 2. 遍历所有 frame
  174. print(" -> work_canvas 中未找到 #q_11,遍历所有 frame...")
  175. for frame in page.frames:
  176. try:
  177. fname = frame.name or frame.url[:60]
  178. count = frame.locator("input#q_11").count()
  179. if count > 0:
  180. target_frame = frame
  181. print(f" -> 在 frame [{fname}] 中找到 #q_11!")
  182. break
  183. # 也试试 #f_panel
  184. if frame.locator("#f_panel").count() > 0:
  185. target_frame = frame
  186. print(f" -> 在 frame [{fname}] 中找到 #f_panel!")
  187. break
  188. except Exception as fe:
  189. continue
  190. if target_frame is None:
  191. # 3. 诊断输出:列出所有 frame 和它们能看到的元素
  192. print(" ⚠️ 所有 frame 中均未找到 #q_11,诊断信息:")
  193. for idx, frame in enumerate(page.frames):
  194. try:
  195. fname = frame.name or "unnamed"
  196. furl = frame.url[:80] if frame.url else "no-url"
  197. has_panel = frame.locator("#f_panel").count()
  198. has_status = frame.locator("#p_status").count()
  199. has_other_qs = frame.locator("#other_qs").count()
  200. has_label = frame.locator("label:has-text('等待订单合并')").count()
  201. print(f" frame[{idx}] name={fname} url={furl}")
  202. print(f" #f_panel={has_panel} #p_status={has_status} #other_qs={has_other_qs} label(等待订单合并)={has_label}")
  203. except:
  204. print(f" frame[{idx}] - 访问失败")
  205. print(" ⚠️ 所有方法均未找到'等待订单合并'复选框。")
  206. else:
  207. # 确保异常子分类区域展开
  208. try:
  209. other_qs = target_frame.locator("#other_qs")
  210. if other_qs.count() > 0 and not other_qs.is_visible():
  211. arrow = target_frame.locator("#oql_")
  212. if arrow.count() > 0:
  213. arrow.click(force=True)
  214. time.sleep(0.5)
  215. print(" -> 已展开'异常'子分类区域。")
  216. except:
  217. pass
  218. # 勾选 checkbox
  219. cb = target_frame.locator("input#q_11")
  220. if cb.count() > 0:
  221. try:
  222. cb.scroll_into_view_if_needed(timeout=3000)
  223. except:
  224. pass
  225. time.sleep(0.3)
  226. if not cb.is_checked():
  227. cb.check(force=True, timeout=5000)
  228. is_checked = True
  229. print(" ✅ 成功勾选'等待订单合并'")
  230. else:
  231. # 用 label 点击
  232. label = target_frame.locator("label[for='q_11']")
  233. if label.count() > 0:
  234. label.click(force=True, timeout=5000)
  235. time.sleep(1)
  236. is_checked = True
  237. print(" ✅ 通过 label 点击勾选'等待订单合并'")
  238. # 如果 target_frame 和 work_canvas 不同,更新 work_canvas 供后续使用
  239. if target_frame is not work_canvas:
  240. print(" -> 更新 work_canvas 为包含筛选面板的 frame。")
  241. work_canvas = target_frame
  242. time.sleep(1.5)
  243. except Exception as e:
  244. print(f" ⚠️ 勾选逻辑执行异常: {e}")
  245. # 熔断判断
  246. if not is_checked:
  247. print(" ❌ [安全熔断] 无法确认‘等待订单合并’勾选状态,任务已终止。")
  248. should_close = False
  249. return False
  250. print(" -> 执行'组合查询'...")
  251. try:
  252. # 使用 locator 代替 evaluate(跨域兼容)
  253. search_btn = work_canvas.locator(".btn-search, span.btn_search").first
  254. if search_btn.count() > 0:
  255. search_btn.click(force=True, timeout=5000)
  256. time.sleep(1)
  257. else:
  258. search_text = work_canvas.locator("span:has-text('组合查询'), button:has-text('组合查询')").first
  259. if search_text.count() > 0:
  260. search_text.click(force=True, timeout=5000)
  261. time.sleep(1)
  262. else:
  263. print(" ⚠️ 未找到'组合查询'按钮。")
  264. time.sleep(2)
  265. except Exception as e:
  266. print(f" ⚠️ 组合查询点击异常: {e}")
  267. print(" -> 勾选全选...")
  268. try:
  269. # 跨 frame 搜索全选框 (可能在不同于筛选面板的 frame 中)
  270. select_frame = None
  271. select_cb = None
  272. # 先在 work_canvas 中找
  273. cb = work_canvas.locator("#_jt_h_checked")
  274. if cb.count() > 0:
  275. select_frame = work_canvas
  276. select_cb = cb
  277. else:
  278. # 遍历所有 frame
  279. for frame in page.frames:
  280. try:
  281. cb = frame.locator("#_jt_h_checked")
  282. if cb.count() > 0:
  283. select_frame = frame
  284. select_cb = cb
  285. fname = frame.name or frame.url[:60]
  286. print(f" -> 在 frame [{fname}] 中找到全选框。")
  287. break
  288. except:
  289. continue
  290. if select_cb is not None:
  291. if not select_cb.is_checked():
  292. select_cb.click(force=True, timeout=5000)
  293. time.sleep(1)
  294. print(" ✅ 成功勾选全选。")
  295. else:
  296. print(" ⚠️ 未发现全选框。")
  297. except Exception as e:
  298. print(f" ⚠️ 全选框操作异常: {e}")
  299. print(" -> 触发'智能合并'...")
  300. try:
  301. # 跨 frame 搜索懒人自动化/智能合并入口
  302. action_frame = select_frame or work_canvas
  303. # 先在 action_frame 中找
  304. menu_loc = action_frame.locator("#ZZ").first
  305. if menu_loc.count() == 0:
  306. # 在所有 frame 中找 #ZZ
  307. for frame in page.frames:
  308. try:
  309. loc = frame.locator("#ZZ")
  310. if loc.count() > 0:
  311. action_frame = frame
  312. menu_loc = loc.first
  313. fname = frame.name or frame.url[:60]
  314. print(f" -> 在 frame [{fname}] 中找到 #ZZ。")
  315. break
  316. except:
  317. continue
  318. if menu_loc.count() == 0:
  319. # 尝试文本匹配
  320. for frame in page.frames:
  321. try:
  322. loc = frame.locator("div:has-text('懒人自动化'), span:has-text('懒人自动化')").first
  323. if loc.count() > 0:
  324. action_frame = frame
  325. menu_loc = loc
  326. break
  327. except:
  328. continue
  329. if menu_loc.count() > 0:
  330. try:
  331. menu_loc.scroll_into_view_if_needed(timeout=3000)
  332. except:
  333. pass
  334. menu_loc.click(force=True)
  335. time.sleep(2) # 等待下拉菜单完全展开
  336. # 点击智能合并 - 等待元素变为可见再点击
  337. merge_clicked = False
  338. merge_selectors = [
  339. ".title_div:has-text('智能合并')",
  340. "div:text-is('智能合并')",
  341. "span:text-is('智能合并')",
  342. "a:text-is('智能合并')",
  343. "div:not(.content_div):has-text('智能合并')",
  344. ]
  345. # 在 action_frame 和所有 frame 中查找可见的智能合并
  346. frames_to_try = [action_frame] + [f for f in page.frames if f is not action_frame]
  347. for fr in frames_to_try:
  348. if merge_clicked:
  349. break
  350. for sel in merge_selectors:
  351. try:
  352. loc = fr.locator(sel).first
  353. if loc.count() > 0:
  354. # 等待元素可见(最多3秒)
  355. try:
  356. loc.wait_for(state="visible", timeout=3000)
  357. except:
  358. pass
  359. if loc.is_visible():
  360. print(f" -> 执行二级点击:智能合并 (selector={sel})")
  361. loc.click(force=True)
  362. time.sleep(1)
  363. merge_clicked = True
  364. break
  365. except:
  366. continue
  367. if not merge_clicked:
  368. print(" ⚠️ 未找到可见的'智能合并'按钮。")
  369. else:
  370. print(" ⚠️ 未发现'懒人自动化'入口(已搜索所有 frame)。")
  371. except Exception as e:
  372. print(f" ⚠️ 智能合并流程故障: {e}")
  373. # 确认弹窗处理
  374. print(" -> 等待确认弹窗...")
  375. time.sleep(3)
  376. confirmed = False
  377. # 确认按钮是 <input type="button" id="confirm_confirm" value="确认">
  378. confirm_selectors = [
  379. "input#confirm_confirm",
  380. "input[value='确认']",
  381. "input[type='button'][value='确认']",
  382. "#confirm_btn_div input.btn_1",
  383. ]
  384. # 搜索所有 frame
  385. for frame in page.frames:
  386. if confirmed:
  387. break
  388. try:
  389. for c_sel in confirm_selectors:
  390. try:
  391. btn = frame.locator(c_sel).first
  392. if btn.count() > 0:
  393. btn.click(force=True, timeout=3000)
  394. time.sleep(1)
  395. fname = frame.name or "main"
  396. print(f" ✅ 成功点击确认按钮 (frame={fname})。")
  397. confirmed = True
  398. break
  399. except:
  400. continue
  401. except:
  402. continue
  403. # 如果没找到,等 3 秒重试
  404. if not confirmed:
  405. time.sleep(3)
  406. for frame in page.frames:
  407. if confirmed:
  408. break
  409. try:
  410. btn = frame.locator("input#confirm_confirm").first
  411. if btn.count() > 0:
  412. btn.click(force=True, timeout=3000)
  413. time.sleep(1)
  414. print(f" ✅ 成功点击确认按钮 (第二轮)。")
  415. confirmed = True
  416. except:
  417. continue
  418. if not confirmed:
  419. print(" ⚠️ 未能自动点击确认弹窗。")
  420. time.sleep(2)
  421. # ========== 智能合并后续流程 ==========
  422. # --- 辅助函数:跨 frame 关闭提示弹窗 (a#msg_close) ---
  423. def close_msg_dialog(description="提示弹窗"):
  424. time.sleep(2)
  425. for frame in page.frames:
  426. try:
  427. close_btn = frame.locator("a#msg_close").first
  428. if close_btn.count() > 0:
  429. close_btn.click(force=True, timeout=3000)
  430. time.sleep(1)
  431. print(f" ✅ 已关闭{description}。")
  432. return True
  433. except:
  434. continue
  435. print(f" ℹ️ 未发现{description}。")
  436. return False
  437. # --- 辅助函数:跨 frame 点击组合查询 ---
  438. def click_search():
  439. for frame in page.frames:
  440. try:
  441. btn = frame.locator(".btn-search, span.btn_search").first
  442. if btn.count() > 0:
  443. btn.click(force=True, timeout=5000)
  444. time.sleep(1)
  445. print(" ✅ 已点击组合查询。")
  446. return True
  447. txt = frame.locator("span:has-text('组合查询'), button:has-text('组合查询')").first
  448. if txt.count() > 0:
  449. txt.click(force=True, timeout=5000)
  450. time.sleep(1)
  451. print(" ✅ 已点击组合查询。")
  452. return True
  453. except:
  454. continue
  455. return False
  456. # --- 辅助函数:跨 frame 全选 ---
  457. def click_select_all():
  458. for frame in page.frames:
  459. try:
  460. cb = frame.locator("#_jt_h_checked").first
  461. if cb.count() > 0:
  462. if not cb.is_checked():
  463. cb.click(force=True, timeout=5000)
  464. time.sleep(1)
  465. print(" ✅ 已全选。")
  466. return True
  467. except:
  468. continue
  469. return False
  470. # --- 辅助函数:跨 frame 查找筛选面板 frame ---
  471. def find_filter_frame():
  472. for frame in page.frames:
  473. try:
  474. if frame.locator("#f_panel").count() > 0 or frame.locator("input#q_11").count() > 0:
  475. return frame
  476. except:
  477. continue
  478. return None
  479. # --- 辅助函数:检查是否有订单数据行 ---
  480. def has_data_rows():
  481. for frame in page.frames:
  482. try:
  483. rows = frame.locator("._jt_row, tr[data-id]").count()
  484. if rows > 0:
  485. print(f" -> 发现 {rows} 条订单数据。")
  486. return True, rows
  487. except:
  488. continue
  489. return False, 0
  490. if confirmed:
  491. # 步骤A: 关闭智能合并成功弹窗
  492. print(" -> 关闭智能合并成功弹窗...")
  493. close_msg_dialog("智能合并成功弹窗")
  494. time.sleep(1)
  495. # 步骤B: 重新组合查询,检查是否有剩余未合并订单
  496. print(" -> 重新组合查询检查剩余订单...")
  497. click_search()
  498. time.sleep(3)
  499. has_remaining, remaining_count = has_data_rows()
  500. if has_remaining:
  501. print(f" -> 还有 {remaining_count} 条剩余订单,执行'转到待审核'...")
  502. # 全选剩余订单
  503. click_select_all()
  504. time.sleep(1)
  505. # 点击"转到待审核"按钮
  506. transfer_clicked = False
  507. transfer_selectors = [
  508. "span#ToWaitConfirm",
  509. "#ToWaitConfirm",
  510. ]
  511. for frame in page.frames:
  512. if transfer_clicked:
  513. break
  514. try:
  515. for sel in transfer_selectors:
  516. loc = frame.locator(sel).first
  517. if loc.count() > 0 and loc.is_visible():
  518. loc.click(force=True, timeout=5000)
  519. time.sleep(1)
  520. print(f" ✅ 已点击'转到待审核' (sel={sel})。")
  521. transfer_clicked = True
  522. break
  523. except:
  524. continue
  525. if transfer_clicked:
  526. # 等待并关闭转到待审核的结果弹窗
  527. print(" -> 等待'转到待审核'结果弹窗...")
  528. time.sleep(3)
  529. close_msg_dialog("转到待审核结果弹窗")
  530. else:
  531. print(" ⚠️ 未找到'转到待审核'按钮。")
  532. else:
  533. print(" -> 没有剩余订单,跳过转审核步骤。")
  534. # ========== 步骤C-E: 全流程处理(执行两遍以防遗漏) ==========
  535. for process_round in range(2):
  536. print(f'\n ========== 开始第 {process_round+1} 轮“已付款待审核”全流程处理 ==========')
  537. # 步骤C: 切换筛选条件 - 取消"等待订单合并",勾选"已付款待审核"
  538. print(" -> 切换筛选条件:取消'等待订单合并',勾选'已付款待审核'...")
  539. filter_frame = find_filter_frame()
  540. if filter_frame:
  541. try:
  542. # 取消勾选"等待订单合并" (q_11)
  543. q11 = filter_frame.locator("input#q_11")
  544. if q11.count() > 0 and q11.is_checked():
  545. q11.click(force=True)
  546. time.sleep(1)
  547. print(" ✅ 已取消勾选'等待订单合并'")
  548. # 取消勾选"异常" (status_question)
  549. question_cb = filter_frame.locator("input#status_question")
  550. if question_cb.count() > 0 and question_cb.is_checked():
  551. question_cb.click(force=True)
  552. time.sleep(1)
  553. print(" ✅ 已取消勾选'异常'状态。")
  554. else:
  555. # 尝试通过 label 判断和点击(兜底)
  556. q_label = filter_frame.locator("label[for='status_question']")
  557. if q_label.count() > 0:
  558. # 某些情况下,无法直接读取 checkbox 的 check 状态,可以通过重新勾选来确保
  559. # 但既然我们能查 input#status_question,优先依赖前面的逻辑
  560. pass
  561. # 勾选"已付款待审核" - 需要找到对应的 checkbox ID
  562. # 常见的状态选择器格式: input#q_XX
  563. # 先尝试通过 label 文本查找
  564. # 勾选"已付款待审核" (input#status_waitconfirm)
  565. paid_checked = False
  566. paid_cb = filter_frame.locator("input#status_waitconfirm")
  567. if paid_cb.count() > 0:
  568. if not paid_cb.is_checked():
  569. paid_cb.click(force=True)
  570. time.sleep(1)
  571. paid_checked = True
  572. print(" ✅ 已勾选'已付款待审核'")
  573. else:
  574. # 兜底:通过 label 点击
  575. paid_label = filter_frame.locator("label[for='status_waitconfirm']")
  576. if paid_label.count() > 0:
  577. paid_label.first.click(force=True)
  578. time.sleep(1)
  579. paid_checked = True
  580. print(" ✅ 已勾选'已付款待审核' (通过 label)。")
  581. else:
  582. print(" ⚠️ 未找到'已付款待审核'选项。")
  583. if not paid_checked:
  584. # 方法2: 通过 #p_status 状态面板中查找
  585. status_inputs = filter_frame.locator("#p_status input[type='checkbox']")
  586. count = status_inputs.count()
  587. for idx in range(count):
  588. inp = status_inputs.nth(idx)
  589. try:
  590. title = inp.get_attribute("title") or ""
  591. if "已付款待审核" in title:
  592. if not inp.is_checked():
  593. inp.click(force=True)
  594. time.sleep(1)
  595. paid_checked = True
  596. print(f" ✅ 已勾选'已付款待审核' (input title)。")
  597. break
  598. except:
  599. continue
  600. if not paid_checked:
  601. print(" ⚠️ 未找到'已付款待审核'选项。")
  602. time.sleep(1)
  603. except Exception as e:
  604. print(f" ⚠️ 切换筛选条件异常: {e}")
  605. else:
  606. print(" ⚠️ 未找到筛选面板 frame。")
  607. # 步骤D: 最后一次组合查询
  608. print(" -> 最终组合查询...")
  609. click_search()
  610. time.sleep(2)
  611. print(" ✅ 智能合并+转审核流程完成,继续处理已付款待审核订单。")
  612. # ========== 步骤E: 已付款待审核订单处理 ==========
  613. print(" -> 步骤E: 处理已付款待审核订单...")
  614. time.sleep(3)
  615. # E1: 全选
  616. print(" -> 全选已付款待审核订单...")
  617. click_select_all()
  618. time.sleep(1)
  619. # E2: 取消勾选含"预售"标记或"补色专用"商品名的订单
  620. print(" -> 取消勾选含'预售'标记或'补色专用'商品的订单...")
  621. uncheck_count = 0
  622. # 在包含表格的 frame 中操作(iframe-132)
  623. table_frame = None
  624. for frame in page.frames:
  625. try:
  626. if frame.locator("#_jt_h_checked").count() > 0:
  627. table_frame = frame
  628. break
  629. except:
  630. continue
  631. if table_frame:
  632. try:
  633. # 等待行数据渲染(组合查询后可能需要时间)
  634. try:
  635. table_frame.locator("._jt_row").first.wait_for(state="attached", timeout=5000)
  636. except:
  637. pass
  638. processed_indices = set()
  639. print(" -> 开始滚动检查并取消勾选...")
  640. for scroll_round in range(20):
  641. rows = table_frame.locator("._jt_row")
  642. row_count = rows.count()
  643. new_rows_in_view = 0
  644. for idx in range(row_count):
  645. try:
  646. row = rows.nth(idx)
  647. row_idx = row.get_attribute("index")
  648. if not row_idx:
  649. row_idx = f"unknown_{idx}_{scroll_round}"
  650. if row_idx in processed_indices:
  651. continue
  652. processed_indices.add(row_idx)
  653. new_rows_in_view += 1
  654. row_text = row.inner_text(timeout=2000)
  655. try:
  656. row_html = row.inner_html(timeout=2000)
  657. except:
  658. row_html = ""
  659. should_uncheck = False
  660. reason = ""
  661. if "预售" in row_text or "预售" in row_html:
  662. should_uncheck = True
  663. reason = "预售"
  664. elif "补色" in row_text or "补色" in row_html:
  665. should_uncheck = True
  666. reason = "补色"
  667. if should_uncheck:
  668. cb = row.locator("input[type='checkbox']._jt_cbx").first
  669. if cb.count() > 0 and cb.is_checked():
  670. cb.click(force=True)
  671. time.sleep(0.5)
  672. uncheck_count += 1
  673. print(f" ✅ 已取消勾选第{row_idx}行 (含'{reason}')。")
  674. except Exception as e:
  675. continue
  676. if new_rows_in_view == 0 and scroll_round > 0:
  677. # 给最后一次机会,再等0.5秒检查一下
  678. time.sleep(0.5)
  679. rows_after_wait = table_frame.locator("._jt_row").count()
  680. if rows_after_wait == 0 or rows_after_wait <= len(processed_indices):
  681. break
  682. if len(processed_indices) >= 50:
  683. print(" -> 已检查满 50 行,停止滚动检查。")
  684. break
  685. # 往下滚动
  686. try:
  687. scroll_target = table_frame.locator("#_jt_body")
  688. if scroll_target.count() > 0:
  689. # 每次向下滚动 400 像素,避免滚得太快跳过中间的数据
  690. scroll_target.first.evaluate("el => el.scrollTop += 400")
  691. time.sleep(1.5) # 给一点时间让虚拟列表渲染 DOM
  692. else:
  693. break
  694. except:
  695. break
  696. print(f" -> 取消勾选完成,共检查 {len(processed_indices)} 行数据,取消了 {uncheck_count} 行。")
  697. except Exception as e:
  698. print(f" ⚠️ 取消勾选异常: {e}")
  699. else:
  700. print(" ⚠️ 未找到表格 frame。")
  701. # E3: 点击"修改发货仓库"按钮
  702. print(" -> 点击'修改发货仓库'...")
  703. wh_clicked = False
  704. wh_selectors = [
  705. "span.ding_db_txt:has-text('修改发货仓库')",
  706. "span[style*='confirm.png']:has-text('修改发货仓库')",
  707. "span:text-is('修改发货仓库')",
  708. "#ChangeWms",
  709. "span._jt_tool:has-text('修改发货仓库')",
  710. "span[onclick*='ChangeWms']",
  711. "span:has-text('修改发货仓库')",
  712. ]
  713. for frame in page.frames:
  714. if wh_clicked:
  715. break
  716. try:
  717. for sel in wh_selectors:
  718. loc = frame.locator(sel).first
  719. if loc.count() > 0 and loc.is_visible():
  720. loc.click(force=True, timeout=5000)
  721. time.sleep(1)
  722. print(f" ✅ 已点击'修改发货仓库'")
  723. wh_clicked = True
  724. break
  725. except:
  726. continue
  727. if not wh_clicked:
  728. print(" ⚠️ 未找到'修改发货仓库'按钮。")
  729. time.sleep(2)
  730. # E4: 在弹窗中选择"依照订单分配策略自动计算" (input#p_2)
  731. if wh_clicked:
  732. print(" -> 选择'依照订单分配策略自动计算'...")
  733. strategy_selected = False
  734. for frame in page.frames:
  735. if strategy_selected:
  736. break
  737. try:
  738. radio = frame.locator("input#p_2")
  739. if radio.count() > 0:
  740. radio.click(force=True, timeout=3000)
  741. time.sleep(1)
  742. print(" ✅ 已选择'依照订单分配策略自动计算'")
  743. strategy_selected = True
  744. except:
  745. continue
  746. if not strategy_selected:
  747. # 兜底: 通过 label 点击
  748. for frame in page.frames:
  749. try:
  750. lbl = frame.locator("label[for='p_2']")
  751. if lbl.count() > 0:
  752. lbl.click(force=True, timeout=3000)
  753. time.sleep(1)
  754. print(" ✅ 已选择'依照订单分配策略自动计算' (通过 label)。")
  755. strategy_selected = True
  756. break
  757. except:
  758. continue
  759. if not strategy_selected:
  760. print(" ⚠️ 未找到'依照订单分配策略自动计算'选项。")
  761. time.sleep(1)
  762. # E5: 点击确认按钮(可能有2次确认弹窗)
  763. for confirm_round in range(2):
  764. print(f" -> 点击确认 (第{confirm_round+1}次)...")
  765. time.sleep(2)
  766. click_confirmed = False
  767. for frame in page.frames:
  768. if click_confirmed:
  769. break
  770. try:
  771. # 支持多种确认按钮:input#confirm_confirm, span#confirmBtn 等
  772. btn = frame.locator("input#confirm_confirm, span#confirmBtn, span.btn_1:has-text('确认'), span.btn_1:has-text('确定'), .layui-layer-btn0, .layui-layer-btn1, a:text-is('确定'), button:text-is('确定'), a:text-is(''), button:text-is(''), a.btn_1:text-is(''), span.btn_1:text-is(''), .layui-layer-btn a:text-is(''), input[type='button'][value=''], input.btn_1[value='']").first
  773. if btn.count() > 0:
  774. btn.click(force=True, timeout=3000)
  775. time.sleep(1)
  776. print(f" ✅ 已点击确认按钮 (第{confirm_round+1}次)。")
  777. click_confirmed = True
  778. except:
  779. continue
  780. if not click_confirmed:
  781. print(f" ℹ️ 第{confirm_round+1}次确认弹窗未出现。")
  782. break
  783. time.sleep(2)
  784. # E6: 关闭可能的成功提示弹窗
  785. print(" -> 关闭操作结果弹窗...")
  786. close_msg_dialog("修改发货仓库结果弹窗")
  787. time.sleep(1)
  788. print(" ✅ 已付款待审核订单处理完成。")
  789. # E7: 点击转正常单
  790. print(" -> 点击'转正常单'...")
  791. unquestion_clicked = False
  792. for frame in page.frames:
  793. try:
  794. btn = frame.locator("span#UnQuestions:has-text('转正常单'), span._jt_tool:has-text('转正常单')").first
  795. if btn.count() > 0 and btn.is_visible():
  796. btn.click(force=True, timeout=3000)
  797. time.sleep(1)
  798. print(" ✅ 已点击'转正常单'")
  799. unquestion_clicked = True
  800. break
  801. except:
  802. continue
  803. if not unquestion_clicked:
  804. print(" ⚠️ 未找到'转正常单'按钮。")
  805. time.sleep(2)
  806. # 处理转正常单可能的提示弹窗(比如只有异常单才可以转正常单的提示)
  807. close_msg_dialog("转正常单结果提示")
  808. time.sleep(1)
  809. # E8: 设快递
  810. print(" -> 点击'设快递'...")
  811. kuaidi_clicked = False
  812. for frame in page.frames:
  813. try:
  814. btn = frame.locator("span._db_txt:text-is('设快递'), span:text-is('设快递')").first
  815. if btn.count() > 0 and btn.is_visible():
  816. btn.click(force=True, timeout=3000)
  817. time.sleep(1)
  818. print(" ✅ 已点击'设快递'")
  819. kuaidi_clicked = True
  820. break
  821. except:
  822. continue
  823. if not kuaidi_clicked:
  824. print(" ⚠️ 未找到'设快递'按钮。")
  825. time.sleep(2)
  826. if kuaidi_clicked:
  827. print(" -> 选择'让系统自动计算'...")
  828. sys_selected = False
  829. for frame in page.frames:
  830. try:
  831. radio = frame.locator("input#SYS[name='lc']").first
  832. if radio.count() > 0:
  833. radio.click(force=True, timeout=3000)
  834. time.sleep(1)
  835. print(" ✅ 已选择'让系统自动计算'")
  836. sys_selected = True
  837. break
  838. except:
  839. continue
  840. if not sys_selected:
  841. # 兜底通过 label 点击
  842. for frame in page.frames:
  843. try:
  844. lbl = frame.locator("label[for='SYS']").first
  845. if lbl.count() > 0:
  846. lbl.click(force=True, timeout=3000)
  847. time.sleep(1)
  848. print(" ✅ 已选择'让系统自动计算' (通过 label)。")
  849. sys_selected = True
  850. break
  851. except:
  852. continue
  853. if not sys_selected:
  854. print(" ⚠️ 未找到'让系统自动计算'选项。")
  855. time.sleep(1)
  856. # 点击设快递的确认按钮(可能有2次)
  857. for confirm_round in range(2):
  858. print(f" -> 点击确认设快递 (第{confirm_round+1}次)...")
  859. time.sleep(2)
  860. click_confirmed = False
  861. for frame in page.frames:
  862. if click_confirmed:
  863. break
  864. try:
  865. # 优先找 input#confirm_confirm, span#confirmBtn, span.btn_1:has-text('确定')
  866. btn = frame.locator("input#confirm_confirm, span#confirmBtn, span.btn_1:has-text('确定'), span:text-is('确定'), .layui-layer-btn0, a:text-is('确定'), button:text-is('确定')").first
  867. if btn.count() > 0:
  868. btn.click(force=True, timeout=3000)
  869. time.sleep(1)
  870. print(f" ✅ 已点击确认按钮 (第{confirm_round+1}次)。")
  871. click_confirmed = True
  872. except:
  873. continue
  874. if not click_confirmed:
  875. print(f" ℹ️ 第{confirm_round+1}次确认弹窗未出现。")
  876. break
  877. time.sleep(2)
  878. # 处理设快递可能出现的成功提示弹窗
  879. close_msg_dialog("设快递结果提示")
  880. time.sleep(1)
  881. # E9: 审核
  882. print(" -> 点击'审核'...")
  883. audit_clicked = False
  884. for frame in page.frames:
  885. try:
  886. # 必须是可见的按钮,并且包含特定的背景图片特征
  887. btn_loc = frame.locator("span.ding_db_txt[style*='Confirm.png']:has-text('审核') >> visible=true")
  888. if btn_loc.count() > 0:
  889. btn = btn_loc.first
  890. btn.evaluate("el => el.click()")
  891. time.sleep(1)
  892. print(" ✅ 已点击'审核' (JS触发)。")
  893. audit_clicked = True
  894. break
  895. except:
  896. continue
  897. if not audit_clicked:
  898. print(" ⚠️ 未找到'审核'按钮。")
  899. time.sleep(2)
  900. # 处理审核结果弹窗
  901. close_msg_dialog("审核结果提示")
  902. # E10: 转异常流程
  903. print(" -> 开始'转异常'流程...")
  904. print(" -> 再次执行组合查询...")
  905. click_search()
  906. time.sleep(3)
  907. print(" -> 滚动检查并只勾选'预售'的订单...")
  908. table_frame = None
  909. for frame in page.frames:
  910. try:
  911. if frame.locator("#_jt_h_checked").count() > 0:
  912. table_frame = frame
  913. break
  914. except:
  915. continue
  916. check_count = 0
  917. if table_frame:
  918. try:
  919. select_all_cb = table_frame.locator("#_jt_h_checked")
  920. if select_all_cb.count() > 0 and select_all_cb.is_checked():
  921. select_all_cb.click(force=True)
  922. time.sleep(1)
  923. try:
  924. table_frame.locator("._jt_row").first.wait_for(state="attached", timeout=5000)
  925. except:
  926. pass
  927. processed_indices = set()
  928. for scroll_round in range(20):
  929. rows = table_frame.locator("._jt_row")
  930. row_count = rows.count()
  931. new_rows_in_view = 0
  932. for idx in range(row_count):
  933. try:
  934. row = rows.nth(idx)
  935. row_idx = row.get_attribute("index")
  936. if not row_idx:
  937. row_idx = f"unknown_{idx}_{scroll_round}"
  938. if row_idx in processed_indices:
  939. continue
  940. processed_indices.add(row_idx)
  941. new_rows_in_view += 1
  942. row_text = row.inner_text(timeout=2000)
  943. try:
  944. row_html = row.inner_html(timeout=2000)
  945. except:
  946. row_html = ""
  947. should_check = False
  948. reason = ""
  949. if "补色" in row_text or "补色" in row_html:
  950. should_check = False
  951. elif "预售" in row_text or "预售" in row_html:
  952. should_check = True
  953. reason = "预售"
  954. if should_check:
  955. cb = row.locator("input[type='checkbox']._jt_cbx").first
  956. if cb.count() > 0 and not cb.is_checked():
  957. cb.click(force=True)
  958. time.sleep(0.5)
  959. check_count += 1
  960. print(f" ✅ 已勾选第{row_idx}行 (含'{reason}')。")
  961. except:
  962. continue
  963. if new_rows_in_view == 0 and scroll_round > 0:
  964. break
  965. if len(processed_indices) >= 50:
  966. print(" -> 已检查满 50 行,停止滚动检查。")
  967. break
  968. try:
  969. scroll_target = table_frame.locator("#_jt_body")
  970. if scroll_target.count() > 0:
  971. scroll_target.first.evaluate("el => el.scrollTop += 400")
  972. time.sleep(1.5)
  973. else:
  974. break
  975. except:
  976. break
  977. except Exception as e:
  978. print(f" ⚠️ 勾选异常订单失败: {e}")
  979. if check_count > 0:
  980. print(" -> 点击'转异常'...")
  981. question_clicked = False
  982. for frame in page.frames:
  983. try:
  984. btn = frame.locator("span#Questions:has-text('转异常'), span._jt_tool:has-text('转异常')").first
  985. if btn.count() > 0 and btn.is_visible():
  986. btn.click(force=True, timeout=3000)
  987. time.sleep(1)
  988. print(" ✅ 已点击'转异常'")
  989. question_clicked = True
  990. break
  991. except:
  992. continue
  993. time.sleep(2)
  994. if question_clicked:
  995. print(" -> 选择'特殊单'...")
  996. special_selected = False
  997. for frame in page.frames:
  998. try:
  999. lbl = frame.locator("label:text-is('特殊单')").first
  1000. if lbl.count() > 0:
  1001. # 1. 强制点击 label
  1002. lbl.click(force=True, timeout=3000)
  1003. # 2. 用 JS 点击父元素 div
  1004. lbl.evaluate("el => { if(el.parentElement) el.parentElement.click(); }")
  1005. # 3. 强制修改底层 input 状态并派发事件
  1006. radio = frame.locator("input[value='特殊单']").first
  1007. if radio.count() > 0:
  1008. radio.evaluate("el => { el.checked = true; el.dispatchEvent(new Event('change', {bubbles: true})); el.dispatchEvent(new Event('click', {bubbles: true})); }")
  1009. time.sleep(1)
  1010. print(" ✅ 已选择'特殊单' (多重触发保障)。")
  1011. special_selected = True
  1012. break
  1013. except:
  1014. continue
  1015. time.sleep(1)
  1016. for confirm_round in range(2):
  1017. print(f" -> 点击确认转异常 (第{confirm_round+1}次)...")
  1018. time.sleep(2)
  1019. click_confirmed = False
  1020. for frame in page.frames:
  1021. if click_confirmed:
  1022. break
  1023. try:
  1024. btn = frame.locator("input#confirm_confirm, span#confirmBtn, span.btn_1:has-text('确认'), span.btn_1:has-text('确定'), .layui-layer-btn0, .layui-layer-btn1, a:text-is('确定'), button:text-is('确定'), a:text-is(''), button:text-is(''), a.btn_1:text-is(''), span.btn_1:text-is(''), .layui-layer-btn a:text-is(''), input[type='button'][value=''], input.btn_1[value='']").first
  1025. if btn.count() > 0:
  1026. btn.click(force=True, timeout=3000)
  1027. time.sleep(1)
  1028. print(f" ✅ 已点击确认按钮 (第{confirm_round+1}次)。")
  1029. click_confirmed = True
  1030. except:
  1031. continue
  1032. if not click_confirmed:
  1033. print(f" ℹ️ 第{confirm_round+1}次确认弹窗未出现。")
  1034. break
  1035. time.sleep(2)
  1036. close_msg_dialog("转异常结果提示")
  1037. time.sleep(1)
  1038. else:
  1039. print(" -> 未发现任何需要转异常的'预售'订单。")
  1040. return True
  1041. except Exception as e:
  1042. print(f" ❌ 任务异常: {e}")
  1043. return False
  1044. finally:
  1045. if should_close:
  1046. page.close()
  1047. print("[ERP自动化] 标签页已关闭。\n")
  1048. if __name__ == "__main__":
  1049. pass