""" 物料信息提取服务实现 实现从页面提取物料信息的具体逻辑 """ import re from playwright.sync_api import TimeoutError as PlaywrightTimeoutError from interfaces.i_material_extractor import IMaterialExtractor from config.ui_constants import UIConstants from models.material_info import MaterialInfo class MaterialExtractor(IMaterialExtractor): """物料信息提取实现""" def __init__(self, config: UIConstants, verbose: bool = True): """ 初始化物料提取器 Args: config: UI 配置 verbose: 是否打印详细日志 """ self.config = config self.verbose = verbose def _print(self, *args, **kwargs): """打印日志(如果 verbose=True)""" if self.verbose: print(*args, **kwargs) def extract_detail_count(self, frame) -> int: """ 提取详细信息数量 Args: frame: 目标 iframe Returns: 详细信息数量 """ detail_element = frame.get_by_text(re.compile(r"^详细信息 \(\d+\)$")) detail_text = detail_element.inner_text() match = re.search(r"详细信息 \((\d+)\)", detail_text) if match: detail_count = int(match.group(1)) self._print(f"详细信息数量: {detail_count}") return detail_count return 0 def extract_detail_status(self, frame) -> str: """ 提取备料状态 Args: frame: 目标 iframe Returns: 备料状态文本 """ detail_element = frame.get_by_text(re.compile(r"^备料状态:.+$")) detail_text = detail_element.inner_text().replace("\n", "") match = re.search(r"^备料状态:(.+)$", detail_text) if match: detail_status = match.group(1) self._print(f"备料状态: {detail_status}") return detail_status return "" def extract_materials(self, frame, count: int): """ 提取所有物料信息 Args: frame: 目标 iframe count: 物料数量 Returns: 物料信息列表 """ materials = [] # 获取展开后的父容器 child_form = frame.locator(self.config.selectors.CARD_TABLE_SIDE_BOX) child_form.wait_for(state="visible", timeout=self.config.timeouts.FORM_VISIBLE) self._print(f"父容器 {self.config.selectors.CARD_TABLE_SIDE_BOX} 已找到") for i in range(count): serial_number = i + 1 id_label_locator = child_form.get_by_text("序号 " + str(serial_number)) id_label_locator.wait_for( state="visible", timeout=self.config.timeouts.SERIAL_VISIBLE ) self._print(f"处理 {id_label_locator.inner_text()}") # 提取物料信息 code = self._extract_material_code(child_form) name = self._extract_material_name(child_form) pending_quantity = self._extract_pending_quantity(child_form) shipped_quantity = self._extract_shipped_quantity(child_form) material = MaterialInfo( serial_number=serial_number, code=code, name=name, pending_quantity=pending_quantity, shipped_quantity=shipped_quantity, ) materials.append(material) # 导航到下一个物料 self._navigate_to_next_material(child_form, i, count) return materials def _extract_material_code(self, child_form) -> str: """提取物料编码""" input_box = ( child_form.locator("div") .filter(has_text=re.compile(r"^材料编码\d{11}$", re.MULTILINE)) .locator("input") .first ) code = input_box.input_value() self._print(f"材料编码: {code}") return code def _extract_material_name(self, child_form) -> str: """提取物料名称""" input_box = ( child_form.locator("div") .filter(has_text=re.compile(r"^材料名称$")) .locator("input[type='text']") ) name = input_box.input_value() self._print(f"材料名称: {name}") return name def _extract_pending_quantity(self, child_form) -> str: """提取累计待发数量""" input_box = ( child_form.locator("div") .filter(has_text=re.compile(r"^累计待发数量$")) .locator("input[type='text']") ) quantity = input_box.input_value() self._print(f"累计待发数量: {quantity}") return quantity def _extract_shipped_quantity(self, child_form) -> str: """提取累计出库数量""" input_box = ( child_form.locator("div") .filter(has_text=re.compile(r"^累计出库数量$")) .locator("input[type='text']") ) quantity = input_box.input_value() self._print(f"累计出库数量: {quantity}") return quantity def _navigate_to_next_material(self, child_form, current_index: int, total_count: int): """导航到下一个物料""" if current_index != total_count - 1: child_form.get_by_role("button").filter(has_text=re.compile(r"^$")).nth(2).click() else: child_form.get_by_role("button").filter(has_text=re.compile(r"^$")).nth(4).click()