1
1
mirror of https://github.com/KenanZhu/AutoLibrary.git synced 2026-06-18 23:43:02 +08:00

Compare commits

..

3 Commits

Author SHA1 Message Date
KenanZhu 04d66346dc fix(ALConfigWidget): optimize the config window usage
add date calendar popup so that user can select
the date more easily
fix some file dialog title display issue
default max diff time change to 30 minutes
2025-11-22 14:23:35 +08:00
KenanZhu f858295af1 refactor(LibChecker): refactor the code of LibChecker to make it more readable and maintainable 2025-11-22 14:16:38 +08:00
KenanZhu cd6c899388 fix(*): optimize the operators' performance when invoking webdriver
we consume the wait time of webdriver and its
implicit wait time
2025-11-22 14:13:23 +08:00
7 changed files with 200 additions and 122 deletions
+5 -3
View File
@@ -91,7 +91,7 @@ class AutoLib(MsgBase):
self.__driver = webdriver.Firefox(service=service, options=edge_options) self.__driver = webdriver.Firefox(service=service, options=edge_options)
case _: case _:
raise Exception(f"不支持的浏览器驱动类型: {self.__driver_type}") raise Exception(f"不支持的浏览器驱动类型: {self.__driver_type}")
self.__driver.implicitly_wait(10) self.__driver.implicitly_wait(1)
self.__driver.execute_script( self.__driver.execute_script(
"Object.defineProperty(navigator, 'webdriver', {get: () => undefined})" "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"
) )
@@ -122,7 +122,7 @@ class AutoLib(MsgBase):
# wait for page load # wait for page load
try: try:
WebDriverWait(self.__driver, 5).until( # title contains "首页" WebDriverWait(self.__driver, 2).until( # title contains "首页"
EC.title_contains("首页") EC.title_contains("首页")
) )
WebDriverWait(self.__driver, 2).until( # username field presence WebDriverWait(self.__driver, 2).until( # username field presence
@@ -147,7 +147,9 @@ class AutoLib(MsgBase):
self, self,
) -> bool: ) -> bool:
self.__driver.get(self.__system_config_reader.get("library/host_url")) url = self.__system_config_reader.get("library/host_url")
url += self.__system_config_reader.get("library/login_url")
self.__driver.get(url)
if not self.__waitResponseLoad(): if not self.__waitResponseLoad():
return False return False
return True return True
+133 -89
View File
@@ -55,7 +55,7 @@ class LibChecker(LibOperator):
) -> bool: ) -> bool:
try: try:
WebDriverWait(self.__driver, 5).until( WebDriverWait(self.__driver, 2).until(
EC.element_to_be_clickable((By.XPATH, "//a[@href='/history?type=SEAT']")) EC.element_to_be_clickable((By.XPATH, "//a[@href='/history?type=SEAT']"))
).click() ).click()
WebDriverWait(self.__driver, 2).until( WebDriverWait(self.__driver, 2).until(
@@ -67,6 +67,44 @@ class LibChecker(LibOperator):
return True return True
def __decodeReserveTime(
self,
time_element
) -> dict:
time_str = time_element.text.strip()
today = datetime.now().date()
if "明天" in time_str:
target_date = today + timedelta(days=1)
date = target_date.strftime("%Y-%m-%d")
elif "今天" in time_str:
target_date = today
date = target_date.strftime("%Y-%m-%d")
elif "昨天" in time_str:
target_date = today - timedelta(days=1)
date = target_date.strftime("%Y-%m-%d")
else:
date_match = re.search(r"(\d{4}-\d{1,2}-\d{1,2})", time_str)
if date_match:
date = date_match.group(1)
else:
date = ""
time_match = re.search(r"(\d{1,2}:\d{2}) -- (\d{1,2}:\d{2})", time_str)
if time_match:
begin_time = time_match.group(1)
end_time = time_match.group(2)
else:
begin_time = ""
end_time = ""
return {
"date": date,
"time": {
"begin": begin_time,
"end": end_time
}
}
def __decodeReserveInfo( def __decodeReserveInfo(
self, self,
info_elements info_elements
@@ -108,42 +146,80 @@ class LibChecker(LibOperator):
By.CSS_SELECTOR, "a" By.CSS_SELECTOR, "a"
) )
except: except:
return None return {
# process time element to get the date string "date": "",
time_str = time_element.text.strip() "time": {"begin": "", "end": ""},
today = datetime.now().date() "info": {"location": "", "status": ""}
if "明天" in time_str: }
target_date = today + timedelta(days=1) time = self.__decodeReserveTime(time_element)
date_str = target_date.strftime("%Y-%m-%d")
elif "今天" in time_str:
target_date = today
date_str = target_date.strftime("%Y-%m-%d")
elif "昨天" in time_str:
target_date = today - timedelta(days=1)
date_str = target_date.strftime("%Y-%m-%d")
else:
date_match = re.search(r"(\d{4}-\d{1,2}-\d{1,2})", time_str)
if date_match:
date_str = date_match.group(1)
else:
date_str = ""
time_match = re.search(r"(\d{1,2}:\d{2}) -- (\d{1,2}:\d{2})", time_str)
if time_match:
begin_time = time_match.group(1)
end_time = time_match.group(2)
else:
time_str = ""
info = self.__decodeReserveInfo(info_elements) info = self.__decodeReserveInfo(info_elements)
return { return {
"date": date_str, "date": time["date"],
"time": { "time": time["time"],
"begin": begin_time,
"end": end_time,
},
"info": info "info": info
} }
def __decodeReserveRecords(
self,
reservations
) -> list:
records = []
for reservation in reservations:
record = self.__decodeReserveRecord(reservation)
if record["date"] == "":
record = None
if record["time"] == {"begin": "", "end": ""}:
record = None
records.append(record)
return records
def __loadReserveRecords(
self
) -> list:
try:
# check if there's any reservation on the date
WebDriverWait(self.__driver, 2).until(
EC.presence_of_element_located((By.CSS_SELECTOR, ".myReserveList > dl"))
)
reservations = self.__driver.find_elements(
By.CSS_SELECTOR, ".myReserveList > dl:not(#moreBlock)"
)
return reservations
except:
self._showTrace("加载预约记录失败 !")
return None
def __showMoreReserveRecords(
self
) -> bool:
# load new reservations if still not sure
try:
WebDriverWait(self.__driver, 0.1).until(
EC.element_to_be_clickable((By.ID, "moreBtn"))
)
except:
# the reservation is the last one
return False
try:
more_btn = self.__driver.find_element(By.ID, "moreBtn")
if more_btn.is_displayed() and more_btn.is_enabled():
self.__driver.execute_script("arguments[0].scrollIntoView(true);", more_btn)
self.__driver.execute_script("arguments[0].click();", more_btn)
return True
else:
self._showTrace("用户无法加载更多预约记录")
return False
except:
self._showTrace("加载更多预约记录失败 !")
return False
def __getReserveRecord( def __getReserveRecord(
self, self,
wanted_date: str, wanted_date: str,
@@ -154,7 +230,6 @@ class LibChecker(LibOperator):
self._showTrace("日期未指定, 无法检查当前预约状态") self._showTrace("日期未指定, 无法检查当前预约状态")
return None return None
self._showTrace(f"正在检查用户在 {wanted_date} 是否有预约状态为 {wanted_status} 的预约记录......") self._showTrace(f"正在检查用户在 {wanted_date} 是否有预约状态为 {wanted_status} 的预约记录......")
date_obj = datetime.strptime(wanted_date, "%Y-%m-%d").date()
checked_count = 0 checked_count = 0
max_check_times = 6 # we only check (4*(6-1)=)20 reservations, the last time cant be checked max_check_times = 6 # we only check (4*(6-1)=)20 reservations, the last time cant be checked
@@ -162,59 +237,30 @@ class LibChecker(LibOperator):
if not self.__navigateToReserveRecordPage(): if not self.__navigateToReserveRecordPage():
return None return None
for _ in range(max_check_times): for _ in range(max_check_times):
try: reservations = self.__loadReserveRecords()
# check if there's any reservation on the date if reservations is None:
WebDriverWait(self.__driver, 2).until(
EC.presence_of_element_located((By.CSS_SELECTOR, ".myReserveList > dl"))
)
reservations = self.__driver.find_elements(
By.CSS_SELECTOR, ".myReserveList > dl:not(#moreBlock)"
)
except:
self._showTrace("加载预约记录失败 !")
return None return None
for i in range(checked_count, len(reservations)): # the last one is load button records = self.__decodeReserveRecords(reservations[checked_count:])
reservation = reservations[i] for record in records:
record = self.__decodeReserveRecord(reservation) checked_count += 1
if record is None: if record is None:
continue continue
record_date = record["date"] # record date is later than the given date, check the next one
record_time = record["time"] if datetime.strptime(record["date"], "%Y-%m-%d").date() >\
status = record["info"]["status"] datetime.strptime(wanted_date, "%Y-%m-%d").date():
location = record["info"]["location"]
if record_date == "" or record_time == {"begin": "", "end": ""}:
continue continue
is_wanted = (status == wanted_status) # record date is earlier than the given date, so there is no wanted record
# reservation is later than the given date, check the next one if datetime.strptime(record["date"], "%Y-%m-%d").date() <\
if datetime.strptime(record_date, "%Y-%m-%d").date() > date_obj: datetime.strptime(wanted_date, "%Y-%m-%d").date():
continue
# reservation is earlier than the given date, can reserve
if datetime.strptime(record_date, "%Y-%m-%d").date() < date_obj:
return None return None
# query the wanted status if record["info"]["status"] == wanted_status:
if is_wanted:
self._showTrace( self._showTrace(
f"寻找到用户第 {i + 1} 条状态为 {wanted_status} 的预约记录, " f"寻找到用户第 {checked_count} 条状态为 {wanted_status} 的预约记录, "
f"详细信息: {record_date} {record_time['begin']} - {record_time['end']} {location}" f"详细信息: {record["date"]} "
f"{record["time"]["begin"]} - {record["time"]["end"]} {record["info"]["location"]}"
) )
return { return record
"index": i, if not self.__showMoreReserveRecords():
"date": record_date,
"time": record_time,
"status": wanted_status
}
checked_count = len(reservations)
# load new reservations if still not sure
try:
more_btn = self.__driver.find_element(By.ID, "moreBtn")
if more_btn.is_displayed() and more_btn.is_enabled():
self.__driver.execute_script("arguments[0].scrollIntoView(true);", more_btn)
self.__driver.execute_script("arguments[0].click();", more_btn)
else:
self._showTrace("用户无法加载更多预约记录")
break
except:
self._showTrace("加载更多预约记录失败 !")
break break
return None return None
@@ -252,21 +298,21 @@ class LibChecker(LibOperator):
if time_diff_seconds < -30*60: if time_diff_seconds < -30*60:
self._showTrace( self._showTrace(
f"用户在 {date} 的预约开始时间为 {begin_time}, " f"用户在 {date} 的预约开始时间为 {begin_time}, "
f"距离当前时间还有 {self.__formatDiffTime(abs(time_diff_seconds))}, 无法签到" f"当前距离预约开始时间还有 {self.__formatDiffTime(abs(time_diff_seconds))}, 无法签到"
) )
return False return False
# before in 30 minutes, can checkin # before in 30 minutes, can checkin
elif -30*60 <= time_diff_seconds < 0: elif -30*60 <= time_diff_seconds < 0:
self._showTrace( self._showTrace(
f"用户在 {date} 的预约开始时间为 {begin_time}, " f"用户在 {date} 的预约开始时间为 {begin_time}, "
f"距离当前时间还有 {self.__formatDiffTime(abs(time_diff_seconds))}, 可以签到" f"当前距离预约开始时间还有 {self.__formatDiffTime(abs(time_diff_seconds))}, 可以签到"
) )
return True return True
# past less than 30 minutes, can checkin # past less than 30 minutes, can checkin
elif 0 <= time_diff_seconds < 30*60: elif 0 <= time_diff_seconds < 30*60 - 5: # spare 5 seconds for the checkin process
self._showTrace( self._showTrace(
f"用户在 {date} 的预约开始时间为 {begin_time}, " f"用户在 {date} 的预约开始时间为 {begin_time}, "
f"当前时间已经 {self.__formatDiffTime(abs(time_diff_seconds))}, 可以签到" f"当前距离预约开始时间已经过去 {self.__formatDiffTime(abs(time_diff_seconds))}, 可以签到"
) )
return True return True
self._showTrace(f"用户在 {date} 没有有效预约记录, 无法签到") self._showTrace(f"用户在 {date} 没有有效预约记录, 无法签到")
@@ -286,17 +332,15 @@ class LibChecker(LibOperator):
time_diff = end_time - datetime.now() time_diff = end_time - datetime.now()
time_diff_seconds = time_diff.total_seconds() time_diff_seconds = time_diff.total_seconds()
# a using record is definitely after the begin time # a using record is definitely after the begin time
if abs(time_diff_seconds) < 120*60: trace_msg = (
self._showTrace(
f"用户在 {date} 的预约结束时间为 {end_time}, " f"用户在 {date} 的预约结束时间为 {end_time}, "
f"距离当前时间还有 {self.__formatDiffTime(abs(time_diff_seconds))}, 可以续约" f"当前距离预约结束时间还有 {self.__formatDiffTime(abs(time_diff_seconds))}"
) )
if abs(time_diff_seconds) < 120*60:
self._showTrace(f"{trace_msg}, 可以续约")
return True return True
else: else:
self._showTrace( self._showTrace(f"{trace_msg}, 无法续约")
f"用户在 {date} 的预约结束时间为 {end_time}, "
f"距离当前时间还有 {self.__formatDiffTime(abs(time_diff_seconds))}, 无法续约"
)
return False return False
self._showTrace(f"用户在 {date} 没有有效预约记录, 无法续约") self._showTrace(f"用户在 {date} 没有有效预约记录, 无法续约")
return False return False
+1 -1
View File
@@ -38,7 +38,7 @@ class LibCheckin(LibOperator):
) -> bool: ) -> bool:
try: try:
WebDriverWait(self.__driver, 5).until( WebDriverWait(self.__driver, 2).until(
EC.presence_of_element_located((By.CLASS_NAME, "ui_dialog")) EC.presence_of_element_located((By.CLASS_NAME, "ui_dialog"))
) )
WebDriverWait(self.__driver, 2).until( WebDriverWait(self.__driver, 2).until(
+3 -3
View File
@@ -41,13 +41,13 @@ class LibLogin(LibOperator):
# wait to verify login success # wait to verify login success
try: try:
WebDriverWait(self.__driver, 5).until( # title contains "自选座位 :: 座位预约系统" WebDriverWait(self.__driver, 2).until( # title contains "自选座位 :: 座位预约系统"
EC.title_contains("自选座位 :: 座位预约系统") EC.title_contains("自选座位 :: 座位预约系统")
) )
WebDriverWait(self.__driver, 3).until( # search button presence WebDriverWait(self.__driver, 2).until( # search button presence
EC.presence_of_element_located((By.ID, "search")) EC.presence_of_element_located((By.ID, "search"))
) )
WebDriverWait(self.__driver, 3).until( # select content presence WebDriverWait(self.__driver, 2).until( # select content presence
EC.presence_of_element_located((By.CLASS_NAME, "selectContent")) EC.presence_of_element_located((By.CLASS_NAME, "selectContent"))
) )
return True return True
+26 -7
View File
@@ -55,13 +55,13 @@ class LibReserve(LibOperator):
) -> bool: ) -> bool:
try: try:
WebDriverWait(self.__driver, 5).until( WebDriverWait(self.__driver, 2).until(
EC.presence_of_element_located((By.CLASS_NAME, "layoutSeat")) EC.presence_of_element_located((By.CLASS_NAME, "layoutSeat"))
) )
title_elements = [] title_elements = []
# reserve failed without title elements, so we need to try # reserve failed without title elements, so we need to try
try: try:
WebDriverWait(self.__driver, 1).until( WebDriverWait(self.__driver, 2).until(
EC.presence_of_element_located((By.CSS_SELECTOR, ".layoutSeat dt")) EC.presence_of_element_located((By.CSS_SELECTOR, ".layoutSeat dt"))
) )
title_elements = self.__driver.find_elements( title_elements = self.__driver.find_elements(
@@ -309,12 +309,12 @@ class LibReserve(LibOperator):
try: try:
# click the trigger element # click the trigger element
WebDriverWait(self.__driver, 5).until( WebDriverWait(self.__driver, 2).until(
EC.element_to_be_clickable(trigger_locator) EC.element_to_be_clickable(trigger_locator)
).click() ).click()
if option_locator: if option_locator:
# select the option element if specified # select the option element if specified
WebDriverWait(self.__driver, 5).until( WebDriverWait(self.__driver, 2).until(
EC.element_to_be_clickable(option_locator) EC.element_to_be_clickable(option_locator)
).click() ).click()
self._showTrace(success_msg) self._showTrace(success_msg)
@@ -386,9 +386,16 @@ class LibReserve(LibOperator):
try: try:
# wait fot seat layout element to load # wait fot seat layout element to load
WebDriverWait(self.__driver, 5).until( WebDriverWait(self.__driver, 2).until(
EC.presence_of_element_located((By.ID, "seatLayout")) EC.presence_of_element_located((By.ID, "seatLayout"))
) )
WebDriverWait(self.__driver, 2).until(
EC.presence_of_all_elements_located((By.CSS_SELECTOR, "li[id^='seat_']"))
)
except:
self._showTrace(f"座位加载失败 !")
return False
try:
all_seats = self.__driver.find_elements( all_seats = self.__driver.find_elements(
By.CSS_SELECTOR, "li[id^='seat_']" By.CSS_SELECTOR, "li[id^='seat_']"
) )
@@ -397,7 +404,7 @@ class LibReserve(LibOperator):
if not seat_id_upper == seat.text.lstrip('0'): if not seat_id_upper == seat.text.lstrip('0'):
continue continue
seat_link = seat.find_element(By.TAG_NAME, "a") seat_link = seat.find_element(By.TAG_NAME, "a")
WebDriverWait(self.__driver, 5).until( WebDriverWait(self.__driver, 2).until(
EC.element_to_be_clickable(seat_link) EC.element_to_be_clickable(seat_link)
) )
seat_link.click() seat_link.click()
@@ -419,6 +426,15 @@ class LibReserve(LibOperator):
prefer_earlier: bool = True prefer_earlier: bool = True
) -> int: ) -> int:
try:
WebDriverWait(self.__driver, 2).until(
EC.presence_of_all_elements_located(
(By.CSS_SELECTOR, f"#{time_id} ul li a")
)
)
except:
self._showTrace(f"{time_type} 选择失败 ! : 当前未查询到可用时间")
return -1
try: try:
all_time_opts = self.__driver.find_elements( all_time_opts = self.__driver.find_elements(
By.CSS_SELECTOR, By.CSS_SELECTOR,
@@ -429,6 +445,9 @@ class LibReserve(LibOperator):
best_actual_diff = None best_actual_diff = None
best_time_opt = None best_time_opt = None
if not all_time_opts:
self._showTrace(f"{time_type} 选择失败 ! : 当前未查询到可用时间")
return -1
for time_opt in all_time_opts: for time_opt in all_time_opts:
time_attr = time_opt.get_attribute("time") time_attr = time_opt.get_attribute("time")
if time_attr == "now": if time_attr == "now":
@@ -544,7 +563,7 @@ class LibReserve(LibOperator):
return False return False
# map page # map page
try: try:
WebDriverWait(self.__driver, 5).until( WebDriverWait(self.__driver, 2).until(
EC.element_to_be_clickable((By.XPATH, "//a[@href='/map']")) EC.element_to_be_clickable((By.XPATH, "//a[@href='/map']"))
).click() ).click()
WebDriverWait(self.__driver, 2).until( WebDriverWait(self.__driver, 2).until(
+11 -13
View File
@@ -261,21 +261,18 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
self.UsernameEdit.setText("") self.UsernameEdit.setText("")
self.PasswordEdit.setText("") self.PasswordEdit.setText("")
self.UserListWidget.setSortingEnabled(True) self.UserListWidget.setSortingEnabled(True)
self.PasswordEdit.setEchoMode(QLineEdit.Password) self.PasswordEdit.setEchoMode(QLineEdit.EchoMode.Password)
self.ShowPasswordCheckBox.setChecked(False) self.ShowPasswordCheckBox.setChecked(False)
self.FloorComboBox.setCurrentIndex(1) # use for the '__init__' to effect the signal
self.FloorComboBox.setCurrentIndex(0) self.FloorComboBox.setCurrentIndex(0)
self.onFloorComboBoxCurrentIndexChanged()
self.DateEdit.setDate(QDate.currentDate()) self.DateEdit.setDate(QDate.currentDate())
self.DateEdit.setMinimumDate(QDate.currentDate()) self.DateEdit.setMinimumDate(QDate.currentDate())
self.DateEdit.setMaximumDate(QDate.currentDate())
if QTime.currentTime() > QTime(18, 0, 0) and QTime.currentTime() < QTime(23, 0, 0):
self.DateEdit.setMaximumDate(QDate.currentDate().addDays(1))
self.BeginTimeEdit.setTime(QTime.currentTime()) self.BeginTimeEdit.setTime(QTime.currentTime())
self.PreferEarlyBeginTimeCheckBox.setChecked(False) self.PreferEarlyBeginTimeCheckBox.setChecked(False)
self.MaxBeginTimeDiffSpinBox.setValue(10) self.MaxBeginTimeDiffSpinBox.setValue(30)
self.EndTimeEdit.setTime(QTime.currentTime().addSecs(120*60)) self.EndTimeEdit.setTime(QTime.currentTime().addSecs(120*60))
self.PreferLateEndTimeCheckBox.setChecked(False) self.PreferLateEndTimeCheckBox.setChecked(False)
self.MaxEndTimeDiffSpinBox.setValue(10) self.MaxEndTimeDiffSpinBox.setValue(30)
self.ExpectDurationSpinBox.setValue(self.BeginTimeEdit.time().secsTo(self.EndTimeEdit.time())/3600) self.ExpectDurationSpinBox.setValue(self.BeginTimeEdit.time().secsTo(self.EndTimeEdit.time())/3600)
self.SatisfyDurationCheckBox.setChecked(False) self.SatisfyDurationCheckBox.setChecked(False)
@@ -528,12 +525,12 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
"seat_id": "", "seat_id": "",
"begin_time": { "begin_time": {
"time": f"{QTime.currentTime().toString("hh:mm")}", "time": f"{QTime.currentTime().toString("hh:mm")}",
"max_diff": 0, "max_diff": 30,
"prefer_early": False "prefer_early": False
}, },
"end_time": { "end_time": {
"time": f"{QTime.currentTime().addSecs(2*3600).toString("hh:mm")}", "time": f"{QTime.currentTime().addSecs(2*3600).toString("hh:mm")}",
"max_diff": 0, "max_diff": 30,
"prefer_early": True "prefer_early": True
}, },
"expect_duration": 2.0, "expect_duration": 2.0,
@@ -621,7 +618,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
browser_driver_path = QFileDialog.getOpenFileName( browser_driver_path = QFileDialog.getOpenFileName(
self, self,
"选择浏览器驱动 - AutoLibrary", "选择浏览器驱动 - AutoLibrary",
self.CurrentSystemConfigEdit.text(), self.BrowseBrowserDriverEdit.text(),
"可执行文件 (*.exe);;所有文件 (*)" "可执行文件 (*.exe);;所有文件 (*)"
)[0] )[0]
if browser_driver_path: if browser_driver_path:
@@ -700,10 +697,11 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
users_config_path = self.ExportUserConfigEdit.text() users_config_path = self.ExportUserConfigEdit.text()
if system_config_path: if system_config_path:
if self.saveConfigs( if self.saveConfigs(
system_config_path, system_config_path, ""
users_config_path=""
): ):
msg += f"系统配置文件已导出到: \n'{system_config_path}'\n" msg += f"系统配置文件已导出到: \n'{system_config_path}'\n"
else:
msg += f"系统配置文件导出失败: \n'{system_config_path}'\n"
if users_config_path: if users_config_path:
if self.saveConfigs( if self.saveConfigs(
"", users_config_path "", users_config_path
@@ -712,7 +710,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
if msg: if msg:
QMessageBox.information( QMessageBox.information(
self, self,
"信息 - AutoLibrary", "提示 - AutoLibrary",
msg msg
) )
+17 -2
View File
@@ -448,11 +448,14 @@
</size> </size>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;期望的预约时长,脚本会尽量满足&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="maximum"> <property name="maximum">
<double>8.000000000000000</double> <double>8.000000000000000</double>
</property> </property>
<property name="stepType">
<enum>QAbstractSpinBox::StepType::AdaptiveDecimalStepType</enum>
</property>
</widget> </widget>
</item> </item>
<item row="11" column="4"> <item row="11" column="4">
@@ -620,6 +623,9 @@
<height>25</height> <height>25</height>
</size> </size>
</property> </property>
<property name="calendarPopup">
<bool>true</bool>
</property>
<property name="date"> <property name="date">
<date> <date>
<year>2025</year> <year>2025</year>
@@ -752,6 +758,9 @@
<property name="maximum"> <property name="maximum">
<number>120</number> <number>120</number>
</property> </property>
<property name="stepType">
<enum>QAbstractSpinBox::StepType::AdaptiveDecimalStepType</enum>
</property>
</widget> </widget>
</item> </item>
<item row="2" column="1"> <item row="2" column="1">
@@ -842,6 +851,9 @@
<property name="maximum"> <property name="maximum">
<number>120</number> <number>120</number>
</property> </property>
<property name="stepType">
<enum>QAbstractSpinBox::StepType::AdaptiveDecimalStepType</enum>
</property>
</widget> </widget>
</item> </item>
<item row="3" column="4"> <item row="3" column="4">
@@ -911,6 +923,9 @@
<height>25</height> <height>25</height>
</size> </size>
</property> </property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;查询座位布局&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text"> <property name="text">
<string/> <string/>
</property> </property>
@@ -1164,7 +1179,7 @@
</size> </size>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;详情请参阅根目录中的 manual.html&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;详情请参阅 &lt;a href=&quot;https://www.autolibrary.cv/docs/manual_lists.html&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#69fcff;&quot;&gt;用户手册&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="whatsThis"> <property name="whatsThis">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>