mirror of
https://github.com/KenanZhu/AutoLibrary.git
synced 2026-06-18 15:33:03 +08:00
refactor(pages): 抽取时间选择策略为 TimeSelectMaker,将 Overlay 基类更名为 Dialog
将 findBestTimeOption 中的预约/续约双分支逻辑抽象为策略模式: - TimeOptionReader 负责从 WebElement 提取时间数据(ReserveTimeReader / RenewTimeReader) - TimeDecisionMaker 执行纯决策算法,零 Selenium 依赖 - TimeSelectMaker 作为工厂统一创建配置好的决策器 - 共享常量 LIBRARY_CLOSE_MINS 统一收敛至 TimeSelectMaker 同时将 Overlay 基类重命名为 Dialog,SeatMapOverlay 同步更名为 SeatMapDialog,保持命名一致性。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -19,16 +19,13 @@ from selenium.webdriver.remote.webdriver import WebDriver
|
||||
from base.MsgBase import MsgBase
|
||||
from pages.MainShell import MainShell
|
||||
from pages.components.RenewDialog import RenewDialog
|
||||
from pages.flows._helpers import (
|
||||
timeStrToMins,
|
||||
minsToTimeStr,
|
||||
findBestTimeOption,
|
||||
)
|
||||
from pages.flows._helpers import timeStrToMins, minsToTimeStr
|
||||
from pages.strategies.timeSelectMaker import TimeSelectMaker
|
||||
|
||||
|
||||
class RenewFlow(MsgBase):
|
||||
|
||||
LIBRARY_CLOSE_MINS = 1410
|
||||
LIBRARY_CLOSE_MINS = TimeSelectMaker.LIBRARY_CLOSE_MINS
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -83,24 +80,26 @@ class RenewFlow(MsgBase):
|
||||
self._showTrace("当前未查询到可用续约时间 !", self.TraceLevel.WARNING)
|
||||
self._shell.refresh()
|
||||
return False
|
||||
best_opt, best_text, actual_diff, free_times = findBestTimeOption(
|
||||
renew_time_opts, target_renew_mins, max_diff, prefer_earlier,
|
||||
is_reserve=False,
|
||||
result = TimeSelectMaker.forRenew().decide(
|
||||
renew_time_opts,
|
||||
target_renew_mins,
|
||||
max_diff,
|
||||
prefer_earlier
|
||||
)
|
||||
if best_opt is not None:
|
||||
best_opt.click()
|
||||
abs_diff = abs(actual_diff)
|
||||
if actual_diff < 0:
|
||||
if result.selected_index >= 0:
|
||||
renew_time_opts[result.selected_index].click()
|
||||
abs_diff = abs(result.actual_diff)
|
||||
if result.actual_diff < 0:
|
||||
relation = f"早了 {abs_diff} 分钟"
|
||||
elif actual_diff > 0:
|
||||
elif result.actual_diff > 0:
|
||||
relation = f"晚了 {abs_diff} 分钟"
|
||||
else:
|
||||
relation = "正好等于 续约时间"
|
||||
self._showTrace(
|
||||
f"选择距离期望续约时间最近的 {best_text}, "
|
||||
f"选择距离期望续约时间最近的 {result.display_text}, "
|
||||
f"与期望续约时间相比 {relation}"
|
||||
)
|
||||
record["time"]["end"] = best_text.strip()
|
||||
record["time"]["end"] = result.display_text.strip()
|
||||
renew_ok_btn.click()
|
||||
self._shell.refresh()
|
||||
return True
|
||||
@@ -109,7 +108,7 @@ class RenewFlow(MsgBase):
|
||||
f"所有可选时间与目标时间相差都超过了 {max_diff} 分钟 !",
|
||||
self.TraceLevel.WARNING,
|
||||
)
|
||||
self._showTrace(f"当前可供续约的时间有: {free_times}")
|
||||
self._showTrace(f"当前可供续约的时间有: {result.free_times}")
|
||||
self._shell.refresh()
|
||||
return False
|
||||
except (NoSuchElementException, TimeoutException) as e:
|
||||
|
||||
@@ -20,11 +20,8 @@ from selenium.webdriver.remote.webdriver import WebDriver
|
||||
|
||||
from base.MsgBase import MsgBase
|
||||
from pages.MainShell import MainShell
|
||||
from pages.flows._helpers import (
|
||||
timeStrToMins,
|
||||
minsToTimeStr,
|
||||
findBestTimeOption,
|
||||
)
|
||||
from pages.flows._helpers import timeStrToMins, minsToTimeStr
|
||||
from pages.strategies.timeSelectMaker import TimeSelectMaker
|
||||
from pages.ReserveView import ReserveView
|
||||
from pages.components.ReserveResultDialog import ReserveResultDialog
|
||||
from pages.components.TimeSelectDialog import TimeSelectDialog
|
||||
@@ -50,7 +47,7 @@ class ReserveContext:
|
||||
|
||||
class ReserveFlow(MsgBase):
|
||||
|
||||
LIBRARY_CLOSE_MINS = timeStrToMins("23:30")
|
||||
LIBRARY_CLOSE_MINS = TimeSelectMaker.LIBRARY_CLOSE_MINS
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -219,30 +216,33 @@ class ReserveFlow(MsgBase):
|
||||
f"{time_type} 选择失败 ! : 当前未查询到可用时间", self.TraceLevel.ERROR
|
||||
)
|
||||
return -1
|
||||
best_opt, best_text, actual_diff, free_times = findBestTimeOption(
|
||||
all_time_opts, target_time, max_time_diff, prefer_earlier, is_reserve=True
|
||||
result = TimeSelectMaker.forReserve().decide(
|
||||
all_time_opts,
|
||||
target_time,
|
||||
max_time_diff,
|
||||
prefer_earlier
|
||||
)
|
||||
if best_opt is not None:
|
||||
best_opt.click()
|
||||
abs_diff = abs(actual_diff)
|
||||
if actual_diff < 0:
|
||||
if result.selected_index >= 0:
|
||||
all_time_opts[result.selected_index].click()
|
||||
abs_diff = abs(result.actual_diff)
|
||||
if result.actual_diff < 0:
|
||||
relation = f"早了 {abs_diff} 分钟"
|
||||
elif actual_diff > 0:
|
||||
elif result.actual_diff > 0:
|
||||
relation = f"晚了 {abs_diff} 分钟"
|
||||
else:
|
||||
relation = f"正好等于 {time_type}"
|
||||
self._showTrace(
|
||||
f"选择距离期望 {time_type} 最近的 {best_text}, "
|
||||
f"选择距离期望 {time_type} 最近的 {result.display_text}, "
|
||||
f"与期望 {time_type} 相比 {relation}"
|
||||
)
|
||||
return target_time + actual_diff
|
||||
return target_time + result.actual_diff
|
||||
target_time_str = minsToTimeStr(target_time)
|
||||
self._showTrace(
|
||||
f"无法选择最近的 {time_type} {target_time_str}, "
|
||||
f"所有可选时间与目标时间相差都超过 {max_time_diff} 分钟",
|
||||
self.TraceLevel.WARNING,
|
||||
)
|
||||
self._showTrace(f"当前可供预约的 {time_type} 有: {free_times}")
|
||||
self._showTrace(f"当前可供预约的 {time_type} 有: {result.free_times}")
|
||||
return -1
|
||||
|
||||
def _calcEndTime(
|
||||
@@ -251,7 +251,7 @@ class ReserveFlow(MsgBase):
|
||||
duration: int,
|
||||
) -> int:
|
||||
|
||||
expect_end_mins = int(begin_mins + duration * 60)
|
||||
expect_end_mins = int(begin_mins + duration*60)
|
||||
if expect_end_mins > self.LIBRARY_CLOSE_MINS:
|
||||
expect_end_mins = self.LIBRARY_CLOSE_MINS
|
||||
self._showTrace(
|
||||
|
||||
@@ -7,9 +7,6 @@ This software is provided "as is", without any warranty of any kind.
|
||||
You may use, modify, and distribute this file under the terms of the MIT License.
|
||||
See the LICENSE file for details.
|
||||
"""
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def timeStrToMins(
|
||||
time_str: str,
|
||||
) -> int:
|
||||
@@ -17,67 +14,9 @@ def timeStrToMins(
|
||||
hour, minute = map(int, time_str.split(":"))
|
||||
return hour * 60 + minute
|
||||
|
||||
|
||||
def minsToTimeStr(
|
||||
mins: int,
|
||||
) -> str:
|
||||
|
||||
hour, minute = divmod(int(mins), 60)
|
||||
return f"{hour:02d}:{minute:02d}"
|
||||
|
||||
|
||||
def findBestTimeOption(
|
||||
time_options: list,
|
||||
target_time: int,
|
||||
max_time_diff: int,
|
||||
prefer_earlier: bool,
|
||||
is_reserve: bool = True,
|
||||
) -> tuple:
|
||||
"""
|
||||
Find the best time option from available WebElement options.
|
||||
|
||||
Returns:
|
||||
(bestElement, bestText, actual_diff, freeTimesList)
|
||||
or (None, None, None, freeTimesList) if no suitable option.
|
||||
"""
|
||||
|
||||
free_times = []
|
||||
best_time_diff = max_time_diff
|
||||
best_actual_diff = None
|
||||
best_time_opt = None
|
||||
|
||||
for time_opt in time_options:
|
||||
if is_reserve:
|
||||
time_attr = time_opt.get_attribute("time")
|
||||
if time_attr == "now":
|
||||
now = datetime.now()
|
||||
time_val = now.hour * 60 + now.minute
|
||||
elif time_attr and time_attr.isdigit():
|
||||
time_val = int(time_attr)
|
||||
else:
|
||||
continue
|
||||
else:
|
||||
time_attr = time_opt.get_attribute("id")
|
||||
if not (time_attr and time_attr.isdigit()):
|
||||
continue
|
||||
time_val = int(time_attr)
|
||||
free_times.append(
|
||||
time_opt.text.strip()
|
||||
if not is_reserve
|
||||
else minsToTimeStr(time_val)
|
||||
)
|
||||
actual_diff = time_val - target_time
|
||||
abs_diff = abs(actual_diff)
|
||||
if abs_diff < best_time_diff or (
|
||||
abs_diff == best_time_diff
|
||||
and (
|
||||
(prefer_earlier and actual_diff <= 0)
|
||||
or (not prefer_earlier and actual_diff >= 0)
|
||||
)
|
||||
):
|
||||
best_time_diff = abs_diff
|
||||
best_actual_diff = actual_diff
|
||||
best_time_opt = time_opt
|
||||
if best_time_opt is not None:
|
||||
return (best_time_opt, best_time_opt.text.strip(), best_actual_diff, free_times)
|
||||
return (None, None, None, free_times)
|
||||
|
||||
Reference in New Issue
Block a user