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

Compare commits

...

16 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
KenanZhu 1038a86aff fix(ALMainWindow): fix the clean up issue of worker thread and config window (concernd commit #389ac88) 2025-11-22 14:11:22 +08:00
KenanZhu 15ea47dd07 docs(*): replace manual.html with new website
we build a new website for AutoLibrary, so there
is no need to keep the old manual.html
2025-11-14 22:43:41 +08:00
KenanZhu 829a8440ad chore(gui): fix some widget length to match the design 2025-11-11 09:14:04 +08:00
KenanZhu 389ac885d3 fix(ALMainWindow): optimize the resource usage of gui
This commit fixes memory management issues in the
ALMainWindow class where config window and
task threads were not properly deleted after use,
leading to continuously increasing memory usage.

The fix ensures that all GUI components are
deleted after close and background threads are
correctly terminated.
2025-11-11 09:05:55 +08:00
KenanZhu 68b61b5c8c feat(AutoLib): new feature 'Auto Check-in' 2025-11-11 09:04:11 +08:00
KenanZhu fd5abb5f1e chore(LibReserve, LibCheckin): *
We use a more clear and structured output message
of reservation.

Complete the LibCheckin for the upcoming new
feature : 'Auto Check-in'
2025-11-11 09:00:20 +08:00
KenanZhu 1f16181aeb fix(LibLogin): more clear error message 2025-11-09 19:52:21 +08:00
KenanZhu f0c25903a3 refactor(LibReserve): optimize the pre-check of reserve
Extract the different pre-checks of reserve to
their separate methods

More clear role of 'satisfy_duration' flag
2025-11-09 19:40:08 +08:00
KenanZhu b24e4f473f fix(LibChecker): chore(LibChecker): introduce a new method to generate more readable output
: add renew checker method for upcoming feature: 'Auto Renew'
2025-11-08 18:34:38 +08:00
KenanZhu 8bb65be0b9 fix(LibChecker): remove the debug print 2025-11-08 18:32:33 +08:00
KenanZhu 631785122b fix(gui.ALConfigWidget): fix the width of web driver path line edit 2025-11-08 18:31:07 +08:00
KenanZhu 82ea40d3dc refactor(LibChecker): fix(AutoLib): extract reservation decode logic into separate function
: adjust default browser size to prevent page scale issues

Extract the reservation decode logic into a separate
function within LibChecker to improve code structure
and enable more structured return values for
decision-making.

Testing of the refactored LibChecker revealed that
page scaling could cause unexpected behavior.
Change the default browser size from
1280x720 to 1920x1080 to resolve this.
2025-11-08 12:53:24 +08:00
KenanZhu 1244084c75 style(*): remove the spare punctuation marks ',' at the end of function parameters 2025-11-08 12:51:33 +08:00
24 changed files with 929 additions and 1546 deletions
+35 -10
View File
@@ -21,6 +21,7 @@ from LibChecker import LibChecker
from LibLogin import LibLogin
from LibLogout import LibLogout
from LibReserve import LibReserve
from LibCheckin import LibCheckin
from ConfigReader import ConfigReader
@@ -30,7 +31,7 @@ class AutoLib(MsgBase):
def __init__(
self,
input_queue: queue.Queue,
output_queue: queue.Queue,
output_queue: queue.Queue
):
super().__init__(input_queue, output_queue)
@@ -52,7 +53,8 @@ class AutoLib(MsgBase):
edge_options.add_argument("--no-sandbox")
edge_options.add_argument("--disable-dev-shm-usage")
edge_options.add_argument("--window-size=1280,720")
# must be 1920x1080, otherwise the page will cause some elements not accessible
edge_options.add_argument("--window-size=1920,1080")
edge_options.add_argument("--remote-allow-origins=*")
# omit ssl errors and verbose log level
@@ -89,7 +91,7 @@ class AutoLib(MsgBase):
self.__driver = webdriver.Firefox(service=service, options=edge_options)
case _:
raise Exception(f"不支持的浏览器驱动类型: {self.__driver_type}")
self.__driver.implicitly_wait(10)
self.__driver.implicitly_wait(1)
self.__driver.execute_script(
"Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"
)
@@ -111,15 +113,16 @@ class AutoLib(MsgBase):
self.__lib_login = LibLogin(self._input_queue, self._output_queue, self.__driver)
self.__lib_logout = LibLogout(self._input_queue, self._output_queue, self.__driver)
self.__lib_reserve = LibReserve(self._input_queue, self._output_queue, self.__driver)
self.__lib_checkin = LibCheckin(self._input_queue, self._output_queue, self.__driver)
def __waitResponseLoad(
self,
self
) -> bool:
# wait for page load
try:
WebDriverWait(self.__driver, 5).until( # title contains "首页"
WebDriverWait(self.__driver, 2).until( # title contains "首页"
EC.title_contains("首页")
)
WebDriverWait(self.__driver, 2).until( # username field presence
@@ -144,7 +147,9 @@ class AutoLib(MsgBase):
self,
) -> 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():
return False
return True
@@ -154,11 +159,11 @@ class AutoLib(MsgBase):
self,
username: str,
password: str,
reserve_info: dict,
reserve_info: dict
) -> int:
# result : 0 - success, 1 - failed, 2 - passed
result = 1
result = 2
# login
if not self.__lib_login.login(
@@ -187,6 +192,26 @@ class AutoLib(MsgBase):
self._showTrace(f"用户 {username} 预约失败 !")
result = 1
else:
self._showTrace(f"用户 {username} 无法预约,已跳过")
result = 2
# checkin
if run_mode["auto_checkin"] and result == 2:
if self.__lib_checker.canCheckin(reserve_info.get("date")):
if self.__lib_checkin.checkin(username):
self._showTrace(f"用户 {username} 签到成功 !")
result = 0
else:
self._showTrace(f"用户 {username} 签到失败 !")
result = 1
else:
self._showTrace(f"用户 {username} 无法签到,已跳过")
result = 2
# renewal
if run_mode["auto_renewal"] and result == 2:
if self.__lib_checker.canRenew(reserve_info.get("date")):
pass
else:
self._showTrace(f"用户 {username} 无法续约,已跳过")
result = 2
# logout
if not self.__lib_logout.logout(
@@ -202,7 +227,7 @@ class AutoLib(MsgBase):
def run(
self,
system_config_reader: ConfigReader,
users_config_reader: ConfigReader,
users_config_reader: ConfigReader
):
self.__system_config_reader = system_config_reader
@@ -245,7 +270,7 @@ class AutoLib(MsgBase):
def close(
self,
self
) -> bool:
if self.__driver:
+243 -104
View File
@@ -39,13 +39,23 @@ class LibChecker(LibOperator):
pass
@staticmethod
def __formatDiffTime(
seconds: float
) -> str:
hours = int(seconds//3600)
minutes = int(seconds%3600//60)
seconds = int(seconds%60)
return f"{hours}{minutes}{seconds}"
def __navigateToReserveRecordPage(
self
) -> bool:
try:
WebDriverWait(self.__driver, 5).until(
WebDriverWait(self.__driver, 2).until(
EC.element_to_be_clickable((By.XPATH, "//a[@href='/history?type=SEAT']"))
).click()
WebDriverWait(self.__driver, 2).until(
@@ -57,6 +67,159 @@ class LibChecker(LibOperator):
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(
self,
info_elements
) -> str:
location = ""
status = ""
for info in info_elements:
if "已预约" in info.text:
status = "已预约"
elif "使用中" in info.text:
status = "使用中"
elif "已完成" in info.text:
status = "已完成"
elif "已结束使用" in info.text:
status = "已结束使用"
elif "已取消" in info.text:
status = "已取消"
elif "失约" in info.text:
status = "失约"
elif "图书馆" in info.text:
location = info.text.strip()
return {
"location": location,
"status": status,
}
def __decodeReserveRecord(
self,
reservation
) -> dict:
try:
time_element = reservation.find_element(
By.CSS_SELECTOR, "dt"
)
info_elements = reservation.find_elements(
By.CSS_SELECTOR, "a"
)
except:
return {
"date": "",
"time": {"begin": "", "end": ""},
"info": {"location": "", "status": ""}
}
time = self.__decodeReserveTime(time_element)
info = self.__decodeReserveInfo(info_elements)
return {
"date": time["date"],
"time": time["time"],
"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(
self,
wanted_date: str,
@@ -66,86 +229,38 @@ class LibChecker(LibOperator):
if wanted_date is None:
self._showTrace("日期未指定, 无法检查当前预约状态")
return None
self._showTrace(f"正在检查用户在日期 {wanted_date} 是否有预约状态为 {wanted_status} 的预约记录......")
date_obj = datetime.strptime(wanted_date, "%Y-%m-%d").date()
self._showTrace(f"正在检查用户在 {wanted_date} 是否有预约状态为 {wanted_status} 的预约记录......")
checked_count = 0
max_check_times = 3 # we only check (3*4=)12 reservations
max_check_times = 6 # we only check (4*(6-1)=)20 reservations, the last time cant be checked
if not self.__navigateToReserveRecordPage():
return None
for _ in range(max_check_times):
try:
# check if there's any reservation on the date
reservations = self.__driver.find_elements(
By.CSS_SELECTOR, ".myReserveList dl"
)
except:
self._showTrace("加载预约记录失败 !")
reservations = self.__loadReserveRecords()
if reservations is None:
return None
for i in range(checked_count, len(reservations) - 1): # the last one is load button
reservation = reservations[i]
try:
time_element = reservation.find_element(
By.CSS_SELECTOR, "dt"
)
info_elements = reservation.find_elements(
By.CSS_SELECTOR, "a"
)
except:
self._showTrace(f"解析第 {i + 1} 条预约记录时发生未知错误 !")
records = self.__decodeReserveRecords(reservations[checked_count:])
for record in records:
checked_count += 1
if record is None:
continue
is_wanted = any(wanted_status in status.text for status in info_elements)
# process time element to get the date string
time_str = time_element.text.strip()
today = datetime.now().date()
if "明天" in time_str:
target_date = today + timedelta(days=1)
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:
self._showTrace(f"无法解析第 {i + 1} 条预约记录的日期 ! 该记录的时间为 {time_str}")
continue
# reservation is later than the given date, check the next one
if datetime.strptime(date_str, "%Y-%m-%d").date() > date_obj:
# record date is later than the given date, check the next one
if datetime.strptime(record["date"], "%Y-%m-%d").date() >\
datetime.strptime(wanted_date, "%Y-%m-%d").date():
continue
# reservation is earlier than the given date, can reserve
if datetime.strptime(date_str, "%Y-%m-%d").date() < date_obj:
# record date is earlier than the given date, so there is no wanted record
if datetime.strptime(record["date"], "%Y-%m-%d").date() <\
datetime.strptime(wanted_date, "%Y-%m-%d").date():
return None
# query the wanted status
if is_wanted:
self._showTrace(f"寻找到第 {i + 1} 条预约记录, 状态为 {wanted_status}")
time_match = re.search(r"(\d{1,2}:\d{2}) -- (\d{1,2}:\d{2})", time_str)
if time_match is None:
self._showTrace(f"无法解析第 {i + 1} 条预约记录的时间 ! 该记录的时间为 {time_str}")
continue
return {
"index": i,
"date": date_str,
"time_str": time_match.group(0),
"status": wanted_status
}
checked_count = len(reservations) - 1
# 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("加载更多预约记录失败 !")
if record["info"]["status"] == wanted_status:
self._showTrace(
f"寻找到用户{checked_count}状态为 {wanted_status} 的预约记录, "
f"详细信息: {record["date"]} "
f"{record["time"]["begin"]} - {record["time"]["end"]} {record["info"]["location"]}"
)
return record
if not self.__showMoreReserveRecords():
break
return None
@@ -159,10 +274,11 @@ class LibChecker(LibOperator):
# then can reserve
if self.__getReserveRecord(date, "已预约") is None:
if self.__getReserveRecord(date, "使用中") is None:
self._showTrace(f"用户在日期 {date} 可以预约")
self._showTrace(f"用户在 {date} 可以预约")
return True
self._showTrace(f"用户在日期 {date} 有使用中的预约, 无法预约")
self._showTrace(f"用户在日期 {date} 已存在有效预约, 无法预约")
self._showTrace(f"用户在 {date} 有使用中的预约, 无法预约")
return False
self._showTrace(f"用户在 {date} 已存在有效预约, 无法预约")
return False
@@ -174,34 +290,57 @@ class LibChecker(LibOperator):
# have a reserved record in the given date
record = self.__getReserveRecord(date, "已预约")
if record is not None:
time_match = re.search(r"(\d{1,2}:\d{2})", record["time_str"])
if time_match:
begin_time = time_match.group(0)
begin_time = datetime.strptime(f"{date} {begin_time}", "%Y-%m-%d %H:%M")
time_diff = datetime.now() - begin_time
time_diff_seconds = time_diff.total_seconds()
# before 30 minutes, cant checkin
if time_diff_seconds < -30*60:
self._showTrace(
f"用户在日期 {date} 的预约开始时间为 {begin_time}, "
f"距离当前时间还有 {abs(time_diff_seconds)/60:.2f} 分钟, 无法签到"
)
return False
# before in 30 minutes, can checkin
elif -30*60 <= time_diff_seconds < 0:
self._showTrace(
f"用户在日期 {date} 的预约开始时间为 {begin_time}, "
f"距离当前时间还有 {abs(time_diff_seconds)/60:.2f} 分钟, 可以签到"
)
return True
# past less than 30 minutes, can checkin
elif 0 <= time_diff_seconds < 30*60:
self._showTrace(
f"用户在日期 {date} 的预约开始时间为 {begin_time}, "
f"当前时间已经 {abs(time_diff_seconds)/60:.2f} 分钟, 可以签到"
)
return True
else:
self._showTrace(f"用户在日期 {date} 的预约时间格式错误, 无法签到")
self._showTrace(f"用户在日期 {date} 有没有有效预约记录, 无法签到")
begin_time = record["time"]["begin"]
begin_time = datetime.strptime(f"{date} {begin_time}", "%Y-%m-%d %H:%M")
time_diff = datetime.now() - begin_time
time_diff_seconds = time_diff.total_seconds()
# before 30 minutes, cant checkin
if time_diff_seconds < -30*60:
self._showTrace(
f"用户在 {date} 的预约开始时间为 {begin_time}, "
f"当前距离预约开始时间还有 {self.__formatDiffTime(abs(time_diff_seconds))}, 无法签到"
)
return False
# before in 30 minutes, can checkin
elif -30*60 <= time_diff_seconds < 0:
self._showTrace(
f"用户在 {date} 的预约开始时间为 {begin_time}, "
f"当前距离预约开始时间还有 {self.__formatDiffTime(abs(time_diff_seconds))}, 可以签到"
)
return True
# past less than 30 minutes, can checkin
elif 0 <= time_diff_seconds < 30*60 - 5: # spare 5 seconds for the checkin process
self._showTrace(
f"用户在 {date} 的预约开始时间为 {begin_time}, "
f"当前距离预约开始时间已经过去 {self.__formatDiffTime(abs(time_diff_seconds))}, 可以签到"
)
return True
self._showTrace(f"用户在 {date} 没有有效预约记录, 无法签到")
return False
def canRenew(
self,
date: str
) -> bool:
# have a using record in the given date
record = self.__getReserveRecord(date, "使用中")
if record is not None:
end_time = record["time"]["end"]
end_time = datetime.strptime(f"{date} {end_time}", "%Y-%m-%d %H:%M")
time_diff = end_time - datetime.now()
time_diff_seconds = time_diff.total_seconds()
# a using record is definitely after the begin time
trace_msg = (
f"用户在 {date} 的预约结束时间为 {end_time}, "
f"当前距离预约结束时间还有 {self.__formatDiffTime(abs(time_diff_seconds))}"
)
if abs(time_diff_seconds) < 120*60:
self._showTrace(f"{trace_msg}, 可以续约")
return True
else:
self._showTrace(f"{trace_msg}, 无法续约")
return False
self._showTrace(f"用户在 {date} 没有有效预约记录, 无法续约")
return False
+69 -2
View File
@@ -34,7 +34,74 @@ class LibCheckin(LibOperator):
def _waitResponseLoad(
self,
self
) -> bool:
pass
try:
WebDriverWait(self.__driver, 2).until(
EC.presence_of_element_located((By.CLASS_NAME, "ui_dialog"))
)
WebDriverWait(self.__driver, 2).until(
EC.presence_of_element_located((By.CLASS_NAME, "resultMessage"))
)
WebDriverWait(self.__driver, 2).until(
EC.element_to_be_clickable((By.CLASS_NAME, "btnOK"))
)
result_message_element = self.__driver.find_element(
By.CLASS_NAME, "resultMessage"
)
ok_btn = self.__driver.find_element(By.CLASS_NAME, "btnOK")
except:
self._showTrace("签到时发生未知错误 !")
return False
print(result_message_element)
result_message = result_message_element.text
if "签到成功" in result_message:
try:
detail_elements = self.__driver.find_elements(
By.CSS_SELECTOR, ".resultMessage dd"
)
except:
pass
if detail_elements:
details = [element.text for element in detail_elements if element.text.strip()]
if len(details) >= 5:
self._showTrace(f"\n"\
f" 签到成功 !\n"\
f" {details[1]}\n"\
f" {details[2]}\n"\
f" {details[3]}\n"\
f" {details[4]}")
else:
self._showTrace(
" 签到成功 !\n"\
" 未获取到签到详情 !")
ok_btn.click()
return True
else:
failure_reason = result_message.replace("签到失败", "").strip()
self._showTrace(f"签到失败: {failure_reason}")
ok_btn.click()
return False
def checkin(
self,
username: str
) -> bool:
if self.__driver is None:
self._showTrace("未提供有效 WebDriver 实例 !")
return False
try:
checkin_btn = WebDriverWait(self.__driver, 2).until(
EC.element_to_be_clickable((By.ID, "btnCheckIn"))
)
except:
self._showTrace(f"用户 {username} 签到界面加载失败 !")
return False
if "disabled" in checkin_btn.get_attribute("class"):
self._showTrace("签到按钮不可用, 可能不在场馆内, 请连接图书馆网络后重试")
return False
checkin_btn.click()
return self._waitResponseLoad()
+1 -1
View File
@@ -34,7 +34,7 @@ class LibCheckout(LibOperator):
def _waitResponseLoad(
self,
self
) -> bool:
pass
+13 -13
View File
@@ -36,30 +36,30 @@ class LibLogin(LibOperator):
def _waitResponseLoad(
self,
self
) -> bool:
# wait to verify login success
try:
WebDriverWait(self.__driver, 5).until( # title contains "自选座位 :: 座位预约系统"
WebDriverWait(self.__driver, 2).until( # 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"))
)
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"))
)
return True
except Exception as e:
self._showTrace(f"登录页面加载失败 ! : {e}")
except:
self._showTrace(f"登录页面加载失败 ! : 用户账号或者密码错误/验证码错误, 具体以页面提示为准")
return False
def __fillLogInElements(
self,
username: str,
password: str,
password: str
) -> bool:
# ensure elements presence and fill them
@@ -77,7 +77,7 @@ class LibLogin(LibOperator):
def __autoRecognizeCaptcha(
self,
self
) -> str:
# auto recognize captcha
@@ -99,7 +99,7 @@ class LibLogin(LibOperator):
def __manualRecognizeCaptcha(
self,
self
) -> str:
# manual recognize captcha
@@ -117,7 +117,7 @@ class LibLogin(LibOperator):
def __refreshCaptcha(
self,
self
):
# refresh captcha
@@ -136,7 +136,7 @@ class LibLogin(LibOperator):
def __solveCaptcha(
self,
auto_captcha: bool = True,
auto_captcha: bool = True
) -> str:
max_attempts = 5
@@ -155,7 +155,7 @@ class LibLogin(LibOperator):
def __fillCaptchaElement(
self,
captcha_text: str,
captcha_text: str
) -> bool:
try:
@@ -174,7 +174,7 @@ class LibLogin(LibOperator):
username: str,
password: str,
max_attempts: int = 5,
auto_captcha: bool = True,
auto_captcha: bool = True
) -> bool:
if self.__driver is None:
+3 -3
View File
@@ -22,7 +22,7 @@ class LibLogout(LibOperator):
self,
input_queue: queue.Queue,
output_queue: queue.Queue,
driver,
driver
):
super().__init__(input_queue, output_queue)
@@ -31,7 +31,7 @@ class LibLogout(LibOperator):
def _waitResponseLoad(
self,
self
) -> bool:
return True
@@ -39,7 +39,7 @@ class LibLogout(LibOperator):
def logout(
self,
username: str,
username: str
) -> bool:
if self.__driver is None:
+2 -2
View File
@@ -17,14 +17,14 @@ class LibOperator(MsgBase):
def __init__(
self,
input_queue: queue.Queue,
output_queue: queue.Queue,
output_queue: queue.Queue
):
super().__init__(input_queue, output_queue)
def _waitResponseLoad(
self,
self
) -> bool:
pass
+1 -1
View File
@@ -28,7 +28,7 @@ class LibRenew(LibOperator):
def _waitResponseLoad(
self,
self
) -> bool:
pass
+161 -103
View File
@@ -55,13 +55,13 @@ class LibReserve(LibOperator):
) -> bool:
try:
WebDriverWait(self.__driver, 5).until(
WebDriverWait(self.__driver, 2).until(
EC.presence_of_element_located((By.CLASS_NAME, "layoutSeat"))
)
title_elements = []
# reserve failed without title elements, so we need to try
try:
WebDriverWait(self.__driver, 1).until(
WebDriverWait(self.__driver, 2).until(
EC.presence_of_element_located((By.CSS_SELECTOR, ".layoutSeat dt"))
)
title_elements = self.__driver.find_elements(
@@ -83,16 +83,12 @@ class LibReserve(LibOperator):
raise
if "预定好了" in title or "预约成功" in title or "操作成功" in title:
if len(contents) >= 6:
date_val = contents[1].split(" : ")[1].strip() if " : " in contents[1] else contents[1].strip()
time_val = contents[2].split(" : ")[1].strip() if " : " in contents[2] else contents[2].strip()
seat_val = contents[3].split(" : ")[1].strip() if " : " in contents[3] else contents[3].strip()
checkin_val = contents[5].strip()
self._showTrace(f"\n"\
f" 预约成功 !\n"\
f" 预约日期: {date_val}, \n"\
f" 预约时间: {time_val}, \n"\
f" 预约座位: {seat_val}, \n"\
f" 签到时间: {checkin_val}")
f" {contents[1]}\n"\
f" {contents[2]}\n"\
f" {contents[3]}\n"\
f" 签到时间 {contents[5]}")
else:
self._showTrace(f"\n"\
f" 预约成功 !\n"\
@@ -119,26 +115,26 @@ class LibReserve(LibOperator):
return f"{hour:02d}:{minute:02d}"
def __checkReserveInfo(
def __containRequiredInfo(
self,
reserve_info: dict
) -> bool:
try:
# check the required information
# reserve_info["place"]
if reserve_info.get("floor") is None:
# must contain the required infomation
if reserve_info.get("floor") is None: # if existence ?
raise ValueError("未指定楼层")
if reserve_info["floor"] not in self.__floor_map:
raise ValueError(f"楼层 '{reserve_info['floor']}' 不存在")
if reserve_info["floor"] not in self.__floor_map: # if in the mao ?
raise ValueError(f"楼层 '{reserve_info['floor']}' 不存在")
if reserve_info.get("room") is None:
raise ValueError("未指定房间")
if reserve_info["room"] not in self.__room_map:
raise ValueError(f"房间 '{reserve_info['room']}' 不存在")
raise ValueError(f"房间 '{reserve_info['room']}' 不存在")
if reserve_info.get("seat_id") is None:
raise ValueError("未指定座位")
if reserve_info["seat_id"] == "":
raise ValueError("未指定座位号")
return True
except ValueError as e:
self._showTrace(
f"预约信息错误 ! : {e}, "\
@@ -146,10 +142,14 @@ class LibReserve(LibOperator):
)
return False
# check and try to fix the time errors
cur_time_str = time.strftime("%Y-%m-%d %H:%M", time.localtime())
cur_date, curr_time = cur_time_str.split()
if not reserve_info.get("date"):
def __isValidDate(
self,
reserve_info: dict
) -> bool:
cur_date = time.strftime("%Y-%m-%d", time.localtime())
if reserve_info.get("date") is None:
reserve_info["date"] = cur_date
self._showTrace(f"预约日期未指定, 自动设置为当前日期: {cur_date}")
else:
@@ -159,94 +159,133 @@ class LibReserve(LibOperator):
f"{reserve_info['date']} 早于当前日期 {cur_date}, 自动设置为当前日期"
)
reserve_info["date"] = cur_date
# check the begin time
begin_time = reserve_info.get("begin_time")
if not begin_time:
reserve_info["begin_time"] = {
"time": curr_time,
"max_diff": 30,
"prefer_early": True
}
self._showTrace(f"开始时间未指定, 自动设置为当前时间: {curr_time}, 最大时间差为 30 分钟, 优先选择更早预约时间")
else:
begin_time = reserve_info["begin_time"]
if "time" not in begin_time:
begin_time["time"] = curr_time
self._showTrace(f"开始时间未指定, 自动设置为当前时间: {curr_time}")
if "max_diff" not in begin_time:
begin_time["max_diff"] = 30
self._showTrace(f"最大时间差未指定, 自动设置为 30 分钟")
if "prefer_early" not in begin_time:
begin_time["prefer_early"] = True
self._showTrace(f"是否优先选择更早预约时间未指定, 自动设置为 True")
expect_duration = reserve_info.get("expect_duration")
if not expect_duration:
return True
def __isValidBeginTime(
self,
reserve_info: dict
) -> bool:
cur_time = time.strftime("%H:%M", time.localtime())
if reserve_info.get("begin_time") is None:
reserve_info["begin_time"] = {}
if "time" not in reserve_info["begin_time"]:
reserve_info["begin_time"]["time"] = cur_time
self._showTrace(f"开始时间未指定, 自动设置为当前时间: {cur_time}")
if "max_diff" not in reserve_info["begin_time"]:
reserve_info["begin_time"]["max_diff"] = 30
self._showTrace(f"开始时间最大时间差未指定, 自动设置为 30 分钟")
if "prefer_early" not in reserve_info["begin_time"]:
reserve_info["begin_time"]["prefer_early"] = True
self._showTrace(f"是否优先选择更早开始时间未指定, 自动设置为 True")
return True
def __isValidExpectDuration(
self,
reserve_info: dict
) -> bool:
if reserve_info.get("expect_duration") is None:
reserve_info["expect_duration"] = 4
expect_duration = 4
self._showTrace("预约持续时间未指定, 使用默认时长为 4 小时")
if not reserve_info.get("satisfy_duration"):
if reserve_info.get("satisfy_duration") is None:
reserve_info["satisfy_duration"] = True
self._showTrace("预约满足时长要求未指定, 默认满足")
# check the end time
if not reserve_info.get("end_time"):
begin_mins = self.__timeToMins(reserve_info["begin_time"]["time"])
end_mins = begin_mins + reserve_info["expect_duration"] * 60
end_time_str = self.__minsToTime(end_mins)
return True
def __isValidEndTime(
self,
reserve_info: dict
) -> bool:
if reserve_info.get("end_time") is None:
reserve_info["end_time"] = {}
if "time" not in reserve_info["end_time"]:
end_mins = self.__timeToMins(reserve_info["begin_time"]["time"])
end_mins = end_mins + int(reserve_info["expect_duration"]*60)
reserve_info["end_time"] = {
"time": end_time_str,
"time": self.__minsToTime(end_mins),
"max_diff": 30,
"prefer_early": False
}
self._showTrace(f"结束时间未指定, 自动设置为开始时间加上期望时长: {end_time_str}, 最大时间差为 30 分钟, 优先选择较晚预约时间")
self._showTrace(
f"结束时间未指定, 自动设置为开始时间加上期望时长: {reserve_info['end_time']['time']}"
)
if "max_diff" not in reserve_info["end_time"]:
reserve_info["end_time"]["max_diff"] = 30
self._showTrace(f"结束时间最大时间差未指定, 自动设置为 30 分钟")
if "prefer_early" not in reserve_info["end_time"]:
reserve_info["end_time"]["prefer_early"] = False
self._showTrace(f"是否优先选择较晚结束时间未指定, 自动设置为 True")
return True
def __finalCheck(
self,
reserve_info: dict
):
begin_time, end_time = reserve_info["begin_time"], reserve_info["end_time"]
begin_mins = self.__timeToMins(begin_time["time"])
end_mins = self.__timeToMins(end_time["time"])
# if end time is earlier than begin_time, exchange them
if end_mins < begin_mins:
self._showTrace(
f"结束时间 {end_time['time']} 早于开始时间 {begin_time['time']}, 自动交换"
)
reserve_info["end_time"] = begin_time
reserve_info["begin_time"] = end_time
begin_time, end_time = reserve_info["begin_time"], reserve_info["end_time"]
begin_mins = self.__timeToMins(begin_time["time"])
end_mins = self.__timeToMins(end_time["time"])
# ensure the end time is not later than 23:30
if end_mins > self.__timeToMins("23:30"):
self._showTrace(
f"结束时间 {end_time['time']} 晚于 23:30, 自动设置为 23:30"
)
reserve_info["end_time"]["time"] = "23:30"
end_mins = self.__timeToMins("23:30")
# ensure the duration is not longer than 8 hours
if reserve_info["satisfy_duration"]:
if reserve_info["expect_duration"] > 8:
self._showTrace(
f"该用户设置了优先满足时长要求, 但是预约期望持续时间 "
f"{reserve_info['expect_duration']} 小时 "
f"超出最大时长 8 小时, 自动设置为 8 小时"
)
reserve_info["expect_duration"] = 8
else:
end_time = reserve_info["end_time"]
if "time" not in end_time:
begin_mins = self.__timeToMins(reserve_info["begin_time"]["time"])
end_mins = begin_mins + reserve_info["expect_duration"] * 60
end_time["time"] = self.__minsToTime(end_mins)
self._showTrace(f"结束时间未指定, 自动设置为开始时间加上期望时长: {end_time['time']}")
if "max_diff" not in end_time:
end_time["max_diff"] = 30
self._showTrace(f"最大时间差未指定, 自动设置为 30 分钟")
if "prefer_early" not in end_time:
end_time["prefer_early"] = False
self._showTrace(f"是否优先选择较早预约时间未指定, 自动设置为 False")
# check the reserve time boundary and fix the errors
#
# get time string for message show
begin_time_str = reserve_info["begin_time"]["time"]
end_time_str = reserve_info["end_time"]["time"]
if end_mins - begin_mins > 8*60:
self._showTrace(
f"该用户未设置优先满足时长要求, 但是检查到预约持续时间 "
f"{int((end_mins - begin_mins)/60)} 小时 "
f"超出最大时长 8 小时, 自动设置为 8 小时"
)
reserve_info["expect_duration"] = 8
reserve_info["end_time"]["time"] = self.__minsToTime(begin_mins + 8*60)
return True
# minute time for check and fix them
begin_mins = self.__timeToMins(begin_time_str)
end_mins = self.__timeToMins(end_time_str)
# ensure begin time is not later than end time
if begin_mins > end_mins:
reserve_info["begin_time"]["time"], reserve_info["end_time"]["time"] = end_time_str, begin_time_str
reserve_info["begin_time"]["prefer_early"], reserve_info["end_time"]["prefer_early"] = \
reserve_info["end_time"]["prefer_early"], reserve_info["begin_time"]["prefer_early"]
self._showTrace("预约开始时间晚于预约结束时间, 自动调换开始时间和结束时间")
def __checkReserveInfo(
self,
reserve_info: dict
) -> bool:
# update the begin_mins and end_mins after swap
begin_time_str, end_time_str = end_time_str, begin_time_str
begin_mins, end_mins = end_mins, begin_mins
# ensure end time is not later than 22:30
max_end_mins = self.__timeToMins("22:30")
if end_mins > max_end_mins:
reserve_info["end_time"]["time"] = "22:30"
end_time_str = "22:30"
end_mins = max_end_mins
self._showTrace("预约结束时间超过 22:30, 自动设置为 22:30")
# ensure expect duration is shorter than 8 hours
max_duration_mins = 8 * 60
duration_mins = end_mins - begin_mins
if duration_mins > max_duration_mins:
new_end_mins = begin_mins + max_duration_mins
reserve_info["end_time"]["time"] = self.__minsToTime(new_end_mins)
self._showTrace("预约持续时间超过8小时, 自动设置为 8 小时")
if not self.__containRequiredInfo(reserve_info):
return False
if not self.__isValidDate(reserve_info):
return False
if not self.__isValidBeginTime(reserve_info):
return False
if not self.__isValidExpectDuration(reserve_info):
return False
if not self.__isValidEndTime(reserve_info):
return False
if not self.__finalCheck(reserve_info):
return False
self._showTrace(
f"预约信息检查完成, 准备预约 "
f"{reserve_info['date']} "
@@ -265,17 +304,17 @@ class LibReserve(LibOperator):
trigger_locator: tuple,
fail_msg: str,
success_msg: str,
option_locator: tuple = None,
option_locator: tuple = None
) -> bool:
try:
# click the trigger element
WebDriverWait(self.__driver, 5).until(
WebDriverWait(self.__driver, 2).until(
EC.element_to_be_clickable(trigger_locator)
).click()
if option_locator:
# select the option element if specified
WebDriverWait(self.__driver, 5).until(
WebDriverWait(self.__driver, 2).until(
EC.element_to_be_clickable(option_locator)
).click()
self._showTrace(success_msg)
@@ -347,9 +386,16 @@ class LibReserve(LibOperator):
try:
# 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"))
)
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(
By.CSS_SELECTOR, "li[id^='seat_']"
)
@@ -358,7 +404,7 @@ class LibReserve(LibOperator):
if not seat_id_upper == seat.text.lstrip('0'):
continue
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)
)
seat_link.click()
@@ -380,6 +426,15 @@ class LibReserve(LibOperator):
prefer_earlier: bool = True
) -> 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:
all_time_opts = self.__driver.find_elements(
By.CSS_SELECTOR,
@@ -390,6 +445,9 @@ class LibReserve(LibOperator):
best_actual_diff = None
best_time_opt = None
if not all_time_opts:
self._showTrace(f"{time_type} 选择失败 ! : 当前未查询到可用时间")
return -1
for time_opt in all_time_opts:
time_attr = time_opt.get_attribute("time")
if time_attr == "now":
@@ -505,7 +563,7 @@ class LibReserve(LibOperator):
return False
# map page
try:
WebDriverWait(self.__driver, 5).until(
WebDriverWait(self.__driver, 2).until(
EC.element_to_be_clickable((By.XPATH, "//a[@href='/map']"))
).click()
WebDriverWait(self.__driver, 2).until(
+3 -3
View File
@@ -16,7 +16,7 @@ class MsgBase:
def __init__(
self,
input_queue: queue.Queue,
output_queue: queue.Queue,
output_queue: queue.Queue
):
self._class_name = self.__class__.__name__
@@ -43,7 +43,7 @@ class MsgBase:
def _waitMsg(
self,
timeout: float = 1.0,
timeout: float = 1.0
) -> str:
try:
@@ -55,7 +55,7 @@ class MsgBase:
def _inputMsg(
self,
timeout: float = 1.0,
timeout: float = 1.0
) -> bool:
try:
Binary file not shown.

Before

Width:  |  Height:  |  Size: 785 KiB

-964
View File
@@ -1,964 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="./AutoLibrary.ico" type="image/x-icon">
<title>AutoLibrary 操作手册</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
}
:root {
--primary: #2c3e50;
--secondary: #3498db;
--accent: #e74c3c;
--light: #f8f9fa;
--dark: #2c3e50;
--gray: #6c757d;
--border: #dee2e6;
}
body {
background-color: #c0c0c0a4;
color: #333;
line-height: 1.6;
}
.manual-container {
display: flex;
max-width: 1400px;
margin: 0 auto;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
min-height: 100vh;
}
.sidebar {
width: 280px;
background: var(--primary);
color: rgba(255, 255, 255, 0.7);
padding: 2rem 1rem;
position: sticky;
top: 0;
height: 100vh;
overflow-y: auto;
}
.sidebar-header {
padding-bottom: 1.5rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
margin-bottom: 1.5rem;
transition: all 0.2s ease;
}
.sidebar-header:hover {
color: white;
}
.sidebar h1 {
font-size: 1.5rem;
margin-bottom: 0.5rem;
}
.sidebar-nav {
list-style: none;
}
.sidebar-nav li {
margin-bottom: 0.5rem;
}
.sidebar-nav a {
color: rgba(255, 255, 255, 0.8);
text-decoration: none;
display: block;
padding: 0.7rem 1rem;
border-radius: 5px;
transition: all 0.2s ease;
}
.sidebar-nav a:hover,
.sidebar-nav a.active {
background: rgba(255, 255, 255, 0.1);
color: white;
}
.content {
flex: 1;
background: rgb(245, 245, 245);
padding: 2rem 3rem;
overflow-y: auto;
max-height: 100vh;
}
.section {
margin-bottom: 1rem;
padding-bottom: 2rem;
border-bottom: 1px solid var(--border);
}
h2 {
color: var(--primary);
margin-bottom: 1.5rem;
padding-bottom: 0.5rem;
border-bottom: 2px solid var(--secondary);
}
h3 {
color: var(--dark);
margin: 1.5rem 0 1rem;
}
.step-container {
counter-reset: step-counter;
}
.step {
display: flex;
margin-bottom: 2rem;
background: var(--light);
border-radius: 8px;
padding: 1.5rem;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
position: relative;
}
.step-number {
counter-increment: step-counter;
background: var(--secondary);
color: white;
width: 36px;
height: 36px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
margin-right: 1.5rem;
flex-shrink: 0;
font-size: 1.1rem;
}
.step-number::before {
content: counter(step-counter);
}
.step-content {
flex: 1;
}
.step-content ol {
padding-left: 1em;
}
.step-content ul {
padding-left: 1em;
}
.step-content li {
line-height: 1.5;
}
.step-image {
background: #f0f0f0;
border-radius: 5px;
padding: 15px;
margin: 1rem 0;
display: inline-block;
text-align: center;
border: 1px solid #ddd;
max-width: 100%;
box-sizing: border-box;
}
.step-image img {
max-width: 60%;
height: auto;
border-radius: 3px;
display: block;
margin: 0 auto;
}
.intro-box {
background: #e3f2fd;
border-left: 4px solid var(--secondary);
padding: 1.5rem;
margin-bottom: 2rem;
border-radius: 0 5px 5px 0;
}
.info {
background: #e3f2fd;
border-left: 4px solid #0783ff;
padding: 1rem;
margin: 1rem 0;
border-radius: 0 5px 5px 0;
}
.important {
background: #fff3cd;
border-left: 4px solid #ffc107;
padding: 1rem;
margin: 1rem 0;
border-radius: 0 5px 5px 0;
}
.warning {
background: #f8d7da;
border-left: 4px solid #dc3545;
padding: 1rem;
margin: 1rem 0;
border-radius: 0 5px 5px 0;
}
.highlight {
background: #e3f2fd;
color: #3498db;
padding: 3px 6px;
border-radius: 3px;
font-size: 0.9rem;
font-family: 'Consolas', monospace;
}
.code-block {
background: #2d2d2d;
color: #f8f8f2;
border: 1px solid #444;
border-left: 4px solid var(--secondary);
padding: 1rem;
margin: 1rem 0;
font-family: 'Consolas', monospace;
white-space: pre-wrap;
border-radius: 15px 15px 5px 5px;
font-size: 0.9rem;
overflow-x: auto;
line-height: 1.4;
}
.code-block .bool { color: #569CD6; }
.code-block .string { color: #CE9178; }
.code-block .number { color: #B5CEA8; }
.code-block .boolean { color: #569CD6; }
.code-block .null { color: #569CD6; }
.code-block .property { color: #9CDCFE; }
.code-block .punctuation { color: #D4D4D4; }
.feature-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1.5rem;
margin-top: 1.5rem;
}
.feature-card {
background: white;
border: 1px solid var(--border);
border-radius: 8px;
padding: 1.5rem;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.feature-card:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
.feature-icon {
font-size: 2rem;
color: var(--secondary);
margin-bottom: 1rem;
}
.download-section {
text-align: center;
padding: 2rem;
background: var(--light);
border-radius: 8px;
margin-top: 2rem;
}
.tabs-container {
margin-top: 0.5rem;
margin-bottom: 1rem;
}
.tab-buttons {
display: flex;
margin-bottom: 0;
border-bottom: 1px solid var(--border);
}
.tab-button {
padding: 0.8rem 1.5rem;
cursor: pointer;
border: none;
background: none;
font-size: 1rem;
color: var(--gray);
border-bottom: 3px solid transparent;
transition: all 0.3s ease;
}
.tab-button.active {
color: var(--secondary);
border-bottom: 3px solid var(--secondary);
}
.tab-content {
background: white;
border-radius: 0 5px 5px 5px;
padding: 1.5rem;
border: 1px solid var(--border);
border-top: none;
min-height: 300px;
display: flex;
align-items: center;
justify-content: center;
}
.tab-pane {
display: none;
width: 100%;
}
.tab-pane.active {
display: flex;
flex-direction: column;
/* align-items: center;
justify-content: center; */
}
.tab-pane img {
max-width: 60%;
max-height: 60%;
border-radius: 5px;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);
}
.btn {
display: inline-block;
background: var(--secondary);
color: white;
padding: 0.8rem 1.5rem;
border-radius: 3px;
text-decoration: none;
font-weight: bold;
transition: all 0.3s ease;
border: none;
cursor: pointer;
}
.btn:hover {
background: #2980b9;
transform: translateY(-2px);
}
.faq-item {
margin-bottom: 1.5rem;
border: 1px solid var(--border);
border-radius: 5px;
overflow: hidden;
}
.faq-question {
padding: 1rem 1.5rem;
background: var(--light);
font-weight: bold;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
}
.faq-answer {
padding: 1rem 1.5rem;
background: rgb(220, 220, 220);
display: none;
}
.faq-item.active .faq-answer {
display: block;
}
.browser-drivers {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
justify-content: space-between;
gap: 1.5rem;
margin: 1.5rem 0;
}
.browser-card {
flex: 1;
background: white;
border: 1px solid var(--border);
border-radius: 8px;
padding: 1.5rem;
text-align: center;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.browser-card:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
.browser-logo {
width: 80px;
height: 80px;
margin: 0 auto 1rem;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8px;
overflow: hidden;
}
.browser-logo img {
max-width: 100%;
max-height: 100%;
}
.browser-card h4 {
margin-bottom: 1rem;
color: var(--primary);
}
.browser-card .btn {
margin-top: 1rem;
}
@media (max-width: 992px) {
.manual-container {
flex-direction: column;
}
.sidebar {
width: 100%;
height: auto;
position: relative;
}
.content {
padding: 1.5rem;
}
.feature-grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="manual-container">
<aside class="sidebar">
<div class="sidebar-header">
<h1>AutoLibrary</h1>
<p>操作手册 alpha-v0.03</p>
</div>
<ul class="sidebar-nav">
<li><a href="#intro" class="active">工具简介</a></li>
<li><a href="#preparation">准备工作</a></li>
<li><a href="#usage">使用步骤</a></li>
<li><a href="#features">功能介绍</a></li>
<li><a href="#troubleshooting">故障排除</a></li>
<li><a href="#faq">常见问题</a></li>
<li><a href="#download">下载安装</a></li>
</ul>
</aside>
<main class="content">
<section id="name" class="section" style="display: flex; align-items: center; gap: 10px;">
<img src="./AutoLibrary.ico" alt="AutoLibrary" style="width: 80px; height: 80px;">
<h1>AutoLibrary</h1>
</section>
<section id="intro" class="section">
<h2>工具简介</h2>
<div class="step">
<div class="step-content">
<div class="intro-box">
<p>AutoLibrary 是一款专为北京建筑大学图书馆设计的自动化工具,旨在帮助学生简化图书馆座位操作流程,节省宝贵时间。</p>
</div>
<p>本工具模拟人工操作,通过简单的界面配置并交互使用。</p>
<h3>工具特点</h3>
<ul>
<p>模拟人工操作,不干扰图书馆系统正常运行</p>
<p>支持多种预约模式,满足不同使用场景</p>
<p>支持多账号批量预约</p>
<p>自动处理验证码,减少人工干预</p>
</ul>
</div>
</div>
</section>
<section id="preparation" class="section">
<h2>准备工作</h2>
<div class="step-container">
<div class="step">
<div class="step-number"></div>
<div class="step-content">
<h3>下载浏览器驱动</h3>
<p>工具需要通过浏览器驱动来控制浏览器,请根据您使用的浏览器下载对应版本的驱动:</p>
<div class="browser-drivers">
<div class="browser-card">
<div class="browser-logo">
<img src="https://edgestatic.azureedge.net/welcome/static/favicon.png" alt="Microsoft Edge">
</div>
<h4>Microsoft Edge</h4>
<p>适用于Windows 10/11系统</p>
<a href="https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/" target="_blank" class="btn">下载驱动</a>
</div>
<div class="browser-card">
<div class="browser-logo">
<img src="https://www.gstatic.cn/devrel-devsite/prod/v154b6c17f7870ab2939b3d571919274f806798dc59971188e1f4183601ea7775/chrome/images/touchicon-180.png" alt="Google Chrome">
</div>
<h4>Google Chrome</h4>
<p>最常用的浏览器</p>
<a href="https://developer.chrome.google.cn/docs/chromedriver/downloads" target="_blank" class="btn">下载驱动</a>
</div>
<div class="browser-card">
<div class="browser-logo">
<img src="https://www.firefox.com/media/img/favicons/firefox/browser/favicon-196x196.59e3822720be.png" alt="Mozilla Firefox">
</div>
<h4>Mozilla Firefox</h4>
<p>开源浏览器</p>
<a href="https://github.com/mozilla/geckodriver/releases" target="_blank" class="btn">下载驱动</a>
</div>
</div>
<div class="info">
<strong>提示:</strong> 浏览器驱动版本必须与您的浏览器版本兼容,否则本工具将无法正常工作。
</div>
</div>
</div>
<div class="step">
<div class="step-number"></div>
<div class="step-content">
<h3>确认驱动路径</h3>
<p>下载驱动后,将浏览器驱动程序的路径通过配置窗口加载到AutoLibrary中。</p>
<p>例如:<span class="highlight">C:\Users\Administrator\Downloads\msedgedriver.exe</span></p>
<div class="step-image">
<img src="./配置窗口-系统配置-浏览器路径选择.png" alt="浏览器驱动路径示意图">
</div>
</div>
</div>
</div>
</section>
<section id="usage" class="section">
<h2>使用步骤</h2>
<div class="step-container">
<div class="step">
<div class="step-number"></div>
<div class="step-content">
<h3>启动工具</h3>
<p>双击运行AutoLibrary.exe文件,工具将启动主界面。</p>
<div class="info">
<strong>提示:</strong>软件首次启动,未初始化配置文件,直接运行脚本会提示失败。
</div>
<div class="step-image">
<img src="./运行主界面.png" alt="运行主界面">
</div>
</div>
</div>
<div class="step">
<div class="step-number"></div>
<div class="step-content">
<h3>配置工具</h3>
<p>对于不同用户的需求,你可以使用两种不同的方式来配置工具</p>
<p>1. 使用界面配置:点击主界面窗口右上角的配置按钮,打开配置窗口。</p>
<div id="use-ui" class="tabs-container">
<div class="tab-buttons">
<button class="tab-button active" data-tab="user-config">用户配置</button>
<button class="tab-button" data-tab="system-config">系统配置</button>
<button class="tab-button" data-tab="other-config">其它</button>
</div>
<div class="tab-content">
<div id="user-config" class="tab-pane active">
<div class="step-image">
<img src="./配置窗口-用户配置.png" alt="配置窗口-用户配置">
</div>
<div class="info">
<strong>提示:</strong>初次运行软件时,用户配置默认为空,需要手动添加。
</div>
<h4>用户列表</h4>
<p>用户列表显示当前配置文件中的所有用户,你可以添加、删除用户。选中用户项以进行详细的配置。</p>
<h4>用户信息</h4>
<ol>
<p><i>-学号:</i>用户的学号。</p>
<p><i>-密码:</i>用户的密码,用户默认密码为000000。</p>
</ol>
<h4>预约信息</h4>
<ol>
<p><i>-日期(YYYY-MM-DD):</i>座位预约日期,默认显示当前日期,无法更改(图书馆19:00-23:00可以预约第二天座位,软件将在18:00-23:00允许用户选择第二天的日期)。</p>
<p><i>-地点:</i>预约座位的地点,默认值为“图书馆”。</p>
<p><i>-楼层:</i>预约座位的楼层,默认值为“二层”。</p>
<p><i>-区域:</i>预约座位的区域,默认值为“二层内环”。</p>
<p><i>-座位号:</i>预约座位的座位号。</p>
<p><i>-开始时间(HH:mm):</i>预约座位的开始时间,默认值为当前时间,可选时间范围为7:30-23:30。</p>
<p><i>-结束时间(HH:mm):</i>预约座位的结束时间,默认值为当前时间加上两个小时,可选时间范围与开始时间相同。</p>
<p><i>-最大时间偏差(分钟):</i>选择的开始/结束时间不可用时,会按照该时间偏差范围寻找最近的可用时间。选择0则表示严格按照选择的时间预约,可选范围为0-120分钟。</p>
<p><i>-优先选择最早/晚:</i>当预约时间列表中存在多个相距最近的可用时间时,选择最早(开始时间)/最晚(结束时间)的时间,不勾选将会按照脚本默认行为选择。</p>
<p><i>-期望时长(小时):</i>预约座位的期望时长,默认值为“2小时”,可选范围为0-8小时。</p>
<p><i>-优先满足期望时长:</i>勾选此项,会优先满足预约时长限制,当座位紧张时可能会导致预约失败。</p>
</ol>
</div>
<div id="system-config" class="tab-pane">
<div class="step-image">
<img src="./配置窗口-系统配置.png" alt="配置窗口-系统配置">
</div>
<h4>图书馆设置</h4>
<p>这里主要包含了关于图书馆的访问网址设置,不需要更改。</p>
<h4>浏览器设置</h4>
<p>主要包含浏览器类别选择(当前支持Edge Chromium和Mozilla Firefox),浏览器驱动路径选择以及无头模式设置。</p>
<ol>
<p><i>-浏览器类别:</i>选择您使用的浏览器类别(Edge Chromium或Mozilla Firefox)。</p>
<p><i>-浏览器驱动路径:</i>点击浏览按钮选择对应浏览器类型和版本的浏览器驱动程序的路径。</p>
<p><i>-无头模式:</i>如果您不希望看到浏览器窗口自动操作,可将无头模式设置为true。</p>
</ol>
<h4>登录设置</h4>
<ol>
<p><i>-自动识别验证码:</i>默认勾选。</p>
<p><i>-登录尝试次数:</i>设置登录尝试的最大次数,默认值为3次。</p>
</ol>
<h4>运行模式</h4>
<ol>
<p><i>-自动预约:</i>脚本按照配置中起始时间和预期时长进行预约,用户如果当天存在有效预约,将自动跳过预约步骤。</p>
<p><i>-自动签到:</i>如果用户在脚本启动时满足图书馆预约条件,将自动签到,如果用户当天无有效预约或不在可签到时间内,则自动跳过。</p>
<p><i>-自动续约:</i>如果用户在脚本启动时满足图书馆预约条件,将自动续约,如果用户当天无有效预约或不在可续约时间内,则自动跳过。</p>
</ol>
</div>
<div id="other-config" class="tab-pane">
<div class="step-image">
<img src="./配置窗口-其它.png" alt="配置窗口-其它">
</div>
<h4>当前配置:</h4>
<p>这里主要显示脚本当前使用的系统配置文件和用户配置文件的路径。你可以使用右侧浏览按钮选择新的配置文件路径。</p>
<h4>导出配置:</h4>
<p>选择导出配置文件的目标路径和文件名,点击‘导出配置文件’按钮,将当前的配置项导出。</p>
</div>
</div>
</div>
<p>2. 使用配置文件:在脚本可执行文件的根目录创建系统配置文件system.json和用户配置文件users.json。</p>
<div id="use-file" class="tabs-container">
<div class="tab-buttons">
<div class="tab-button active" data-tab="system.config">系统配置文件</div>
<div class="tab-button" data-tab="users.config">用户配置文件</div>
</div>
<div class="tab-content">
<div id="system.config" class="tab-pane active">
<p>system.json文件控制工具的基本运行参数:</p>
<div class="code-block">
{
<span class="property">"library"</span>: {
<span class="property">"host_url"</span>: <span class="string">"http://10.1.20.7"</span>,
<span class="property">"login_url"</span>: <span class="string">"/login"</span>
},
<span class="property">"mode"</span>: {
<span class="property">"run_mode"</span>: <span class="number">1</span>
},
<span class="property">"login"</span>: {
<span class="property">"auto_captcha"</span>: <span class="bool">true</span>,
<span class="property">"max_attempt"</span>: <span class="number">3</span>
},
<span class="property">"web_driver"</span>: {
<span class="property">"driver_type"</span>: <span class="string">"edge"</span>,
<span class="property">"driver_path"</span>: <span class="string">"msedgedriver.exe"</span>,
<span class="property">"headless"</span>: <span class="bool">false</span>
}
}
</div>
<h4>参数说明</h4>
<ol>
<p><strong>library/host_url</strong>: 图书馆主机URL,无需更改。</p>
<p><strong>library/login_url</strong>: 登录页面URL,无需更改。</p>
<p><strong>mode/run_mode</strong>: 运行模式,可组合使用(+1:自动预约/+2:自动签到/+4:自动续约)</p>
<p><strong>login/auto_captcha</strong>: 自动验证码识别,建议保持true</p>
<p><strong>login/max_attempt</strong>: 登录尝试次数,默认3次</p>
<p><strong>web_driver/driver_type</strong>: 浏览器类型(edge/chrome/firefox</p>
<p><strong>web_driver/driver_path</strong>: 驱动文件路径</p>
<p><strong>web_driver/headless</strong>: 无头模式,默认false运行时显示浏览器窗口</p>
</ol>
</div>
<div id="users.config" class="tab-pane">
<p>users.json文件控制用户的预约和签到参数:</p>
<div class="code-block">
{
<span class="property">"users"</span>: [
{
<span class="property">"username"</span>: <span class="string">"您的学号"</span>,
<span class="property">"password"</span>: <span class="string">"您的密码"</span>,
<span class="property">"reserve_info"</span>: {
<span class="property">"date"</span>: <span class="string">"2025-10-30"</span>,
<span class="property">"place"</span>: <span class="string">"1"</span>,
<span class="property">"floor"</span>: <span class="string">"4"</span>,
<span class="property">"room"</span>: <span class="string">"5"</span>,
<span class="property">"begin_time"</span>: {
<span class="property">"time"</span>: <span class="string">"09:30"</span>,
<span class="property">"max_diff"</span>: <span class="number">30</span>,
<span class="property">"prefer_early"</span>: <span class="bool">true</span>
},
<span class="property">"end_time"</span>: {
<span class="property">"time"</span>: <span class="string">"21:23"</span>,
<span class="property">"max_diff"</span>: <span class="number">30</span>,
<span class="property">"prefer_early"</span>: <span class="bool">false</span>
},
<span class="property">"seat_id"</span>: <span class="string">"31A"</span>,
<span class="property">"expect_duration"</span>: <span class="number">6</span>
<span class="property">"satisfy_duration"</span>: <span class="bool">true</span>
}
},
/* 可以添加多个上述的配置块,每个用户预约信息独立配置 */
]
}
</div>
<h4>参数说明</h4>
<ol>
<p><strong>username</strong>: 学号</p>
<p><strong>password</strong>: 密码</p>
<p><strong>reserve_info/date</strong>: 预约日期(格式:YYYY-MM-DD</p>
<p><strong>reserve_info/place</strong>: 图书馆或者字符“1”</p>
<p><strong>reserve_info/floor</strong>: 预约楼层(“2”:二层,“3”:三层,“4”:四层,“5”:五层)</p>
<p><strong>reserve_info/room</strong>: 预约房间()</p>
<p><strong>reserve_info/seat_id</strong>: 座位编号(例如:“12A/12a/012A/012a”)</p>
<p><strong>reserve_info/begin_time</strong>: 预约开始时间(格式:HH:mm</p>
<p><strong>reserve_info/begin_time/max_diff</strong>: 最大时间差(分钟)</p>
<p><strong>reserve_info/begin_time/prefer_early</strong>: 是否优先预约较早时间(默认true)</p>
<p><strong>reserve_info/end_time</strong>: 预约结束时间(格式:HH:mm</p>
<p><strong>reserve_info/end_time/max_diff</strong>: 最大时间差(分钟)</p>
<p><strong>reserve_info/end_time/prefer_early</strong>: 是否优先预约较早时间(默认true)</p>
<p><strong>reserve_info/expect_duration</strong>: 期望使用时长(小时)</p>
<p><strong>reserve_info/satisfy_duration</strong>: 是否满足期望时长(默认true</p>
</ol>
<div class="info">
<strong>提示:</strong> 可以添加多个用户,工具会按顺序处理每个用户的预约请求。
</div>
</div>
</div>
</div>
</div>
</div>
<div class="step">
<div class="step-number"></div>
<div class="step-content">
<h3>监控运行状态</h3>
<p>如果系统设置中没有勾选浏览器无头模式运行,工具会在运行过程中打开浏览器窗口,显示自动运行过程。</p>
<p>除此之外,你还可以通过软件的运行日志输出区域查看详细的运行状态和错误信息。</p>
<div class="step-image">
<img src="./监控运行状态-运行图.png" alt="监控运行状态">
</div>
</div>
</div>
<div class="step">
<div class="step-number"></div>
<div class="step-content">
<h3>查看运行结果</h3>
<p>软件运行结束后日志会显示本次运行结果:“处理完成, 共计 n 个用户, 成功 n 个用户, 失败 m 个用户”。</p>
<div class="step-image">
<img src="./监控运行状态-运行结果.png" alt="查看运行结果">
</div>
</div>
</div>
</div>
</section>
<section id="features" class="section">
<h2>功能介绍</h2>
<div class="feature-grid">
<div class="feature-card">
<div class="feature-icon"></div>
<h3>自动预约</h3>
<p>如果用户当前没有有效预约时,工具会自动为您预约指定座位。</p>
<div class="info">
<strong>适用场景:</strong> 提前预约第二天的座位
</div>
</div>
<div class="feature-card">
<div class="feature-icon"></div>
<h3>自动签到</h3>
<p>如果用户当前已有预约,且在可签到时间范围(开始时间的前后30分钟)内,工具会自动完成签到。</p>
<div class="info">
<strong>适用场景:</strong> 因忘记签到而导致失约,影响正常使用
</div>
</div>
<div class="feature-card">
<div class="feature-icon">🔄</div>
<h3>自动续约</h3>
<p>如果用户当前正在使用座位,且到达可续约时间(结束时间前的120分钟),工具会自动延长使用时间。</p>
<div class="info">
<strong>适用场景:</strong> 需要长时间使用座位
</div>
</div>
</div>
<h3>模式组合使用</h3>
<p>运行模式可以组合使用,只需在配置窗口中勾选对应模式即可:</p>
<ul>
<li>
<ol><strong>自动预约 + 自动签到 + 自动续约(推荐)</strong></ol>
<ol>自动预约</ol>
<ol>自动预约 + 自动签到</ol>
</ul>
</section>
<section id="troubleshooting" class="section">
<h2>故障排除</h2>
<h3>常见问题及解决方法</h3>
<div class="faq-item">
<div class="faq-question">工具启动时报错"无法找到驱动/Unable to obtain driver"等类似报错信息</div>
<div class="faq-answer">
<p>这是大概率是因为浏览器驱动未正确安装或版本不匹配。</p>
<ul>
<ol>1,检查驱动文件是否放置在正确位置</ol>
<ol>2,确认驱动版本与浏览器版本完全匹配,例如:Chrome浏览器需要对应版本的chromedriver.exe,切勿混用</ol>
<ol>3,尝试重新下载并安装驱动</ol>
</ul>
</div>
</div>
<div class="faq-item">
<div class="faq-question">登录失败,提示账号密码错误</div>
<div class="faq-answer">
<p>请检查配置界面中的账号密码是否正确。</p>
<ul>
<ol>1,确认学号和密码无误</ol>
<ol>2,检查是否有不支持的特殊字符需要转义</ol>
<ol>3,尝试手动登录图书馆系统确认账号可用</ol>
</ul>
</div>
</div>
<div class="faq-item">
<div class="faq-question">预约失败,提示座位不可用</div>
<div class="faq-answer">
<p>目标座位可能已被他人预约或不在可预约时间。</p>
<ul>
<ol>1,确认座位编号是否正确,是否在该楼层指定区域</ol>
<ol>2,尝试预约其它座位或调整预约时间,例如调整允许的开始或结束时间的最大偏差,位置紧张情况下可以让脚本根据允许的时间范围选择最佳起始时间</ol>
</ul>
</div>
</div>
</section>
<section id="faq" class="section">
<h2>常见问题</h2>
<div class="faq-item">
<div class="faq-question">使用AutoLibrary是否安全?</div>
<div class="faq-answer">
<p>AutoLibrary完全模拟人工操作,不干扰图书馆系统正常运行。工具不会收集或上传您的个人信息,所有数据仅保存在本地配置文件中。</p>
</div>
</div>
<div class="faq-item">
<div class="faq-question">可以同时预约多个座位吗?</div>
<div class="faq-answer">
<p>根据图书馆规定,每个账号同一时间段只能预约一个座位。但您可以在配置界面中添加多个账号,工具会依次处理每个账号的预约请求。</p>
<div class="important">
<p><strong>重要:</strong>本工具软件旨在简化并辅助用户正常使用时的图书馆服务流程,请勿滥用影响他人及图书馆正常运行。</p>
</div>
</div>
</div>
<div class="faq-item">
<div class="faq-question">工具运行期间可以操作电脑吗?</div>
<div class="faq-answer">
<p>可以正常使用电脑,但请勿操作工具自动打开的浏览器窗口,否则可能会干扰工具的正常运行。</p>
</div>
</div>
</section>
<section id="download" class="section">
<h2>下载安装</h2>
<div class="download-section">
<h3>获取AutoLibrary</h3>
<p>点击下方按钮下载最新版本的AutoLibrary压缩包:</p>
<a href="#" class="btn">下载 AutoLibrary alpha-v0.03</a>
<div class="info" style="margin-top: 1.5rem;">
<p>文件大小:约98MB</p>
<p>系统要求:Windows 10/11,支持Edge/Chrome/Firefox浏览器</p>
</div>
</div>
<h3>安装步骤</h3>
<ol>
<ol>下载压缩包并解压到任意文件夹</ol>
<ol>根据您使用的浏览器下载对应版本的驱动</ol>
<ol>按照本手册说明配置账号密码等参数</ol>
<ol>点击启动脚本,即可开始自动预约和使用座位</ol>
</ol>
</section>
</main>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.sidebar-nav a').forEach(link => {
link.addEventListener('click', function(e) {
e.preventDefault();
document.querySelectorAll('.sidebar-nav a').forEach(a => {
a.classList.remove('active');
});
this.classList.add('active');
const targetId = this.getAttribute('href');
const targetSection = document.querySelector(targetId);
if (targetSection) {
targetSection.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
});
});
document.querySelectorAll('.tabs-container').forEach(container => {
const tabButtons = container.querySelectorAll('.tab-button');
const tabPanes = container.querySelectorAll('.tab-pane');
tabButtons.forEach(button => {
button.addEventListener('click', function() {
const containerButtons = this.closest('.tabs-container').querySelectorAll('.tab-button');
const containerPanes = this.closest('.tabs-container').querySelectorAll('.tab-pane');
containerButtons.forEach(btn => {
btn.classList.remove('active');
});
this.classList.add('active');
const tabId = this.getAttribute('data-tab');
containerPanes.forEach(pane => {
pane.classList.remove('active');
});
document.getElementById(tabId).classList.add('active');
});
});
});
document.querySelectorAll('.faq-question').forEach(question => {
question.addEventListener('click', function() {
const faqItem = this.parentElement;
faqItem.classList.toggle('active');
});
});
const sections = document.querySelectorAll('.section');
const navLinks = document.querySelectorAll('.sidebar-nav a');
const observerOptions = {
root: null,
rootMargin: '-45% 0px -45% 0px',
threshold: 0
};
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const id = entry.target.getAttribute('id');
navLinks.forEach(link => {
link.classList.remove('active');
});
const activeLink = document.querySelector(`.sidebar-nav a[href="#${id}"]`);
if (activeLink) {
activeLink.classList.add('active');
}
}
});
}, observerOptions);
sections.forEach(section => {
observer.observe(section);
});
});
</script>
</body>
</html>
+1
View File
@@ -0,0 +1 @@
For more infomation, please visit our website: https://www.autolibrary.cv
Binary file not shown.

Before

Width:  |  Height:  |  Size: 852 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

+12 -14
View File
@@ -261,21 +261,18 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
self.UsernameEdit.setText("")
self.PasswordEdit.setText("")
self.UserListWidget.setSortingEnabled(True)
self.PasswordEdit.setEchoMode(QLineEdit.Password)
self.PasswordEdit.setEchoMode(QLineEdit.EchoMode.Password)
self.ShowPasswordCheckBox.setChecked(False)
self.FloorComboBox.setCurrentIndex(1) # use for the '__init__' to effect the signal
self.FloorComboBox.setCurrentIndex(0)
self.onFloorComboBoxCurrentIndexChanged()
self.DateEdit.setDate(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.PreferEarlyBeginTimeCheckBox.setChecked(False)
self.MaxBeginTimeDiffSpinBox.setValue(10)
self.MaxBeginTimeDiffSpinBox.setValue(30)
self.EndTimeEdit.setTime(QTime.currentTime().addSecs(120*60))
self.PreferLateEndTimeCheckBox.setChecked(False)
self.MaxEndTimeDiffSpinBox.setValue(10)
self.MaxEndTimeDiffSpinBox.setValue(30)
self.ExpectDurationSpinBox.setValue(self.BeginTimeEdit.time().secsTo(self.EndTimeEdit.time())/3600)
self.SatisfyDurationCheckBox.setChecked(False)
@@ -528,12 +525,12 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
"seat_id": "",
"begin_time": {
"time": f"{QTime.currentTime().toString("hh:mm")}",
"max_diff": 0,
"max_diff": 30,
"prefer_early": False
},
"end_time": {
"time": f"{QTime.currentTime().addSecs(2*3600).toString("hh:mm")}",
"max_diff": 0,
"max_diff": 30,
"prefer_early": True
},
"expect_duration": 2.0,
@@ -569,7 +566,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
@Slot()
def onFloorComboBoxCurrentIndexChanged(
self,
self
):
floor = self.FloorComboBox.currentText()
@@ -621,7 +618,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
browser_driver_path = QFileDialog.getOpenFileName(
self,
"选择浏览器驱动 - AutoLibrary",
self.CurrentSystemConfigEdit.text(),
self.BrowseBrowserDriverEdit.text(),
"可执行文件 (*.exe);;所有文件 (*)"
)[0]
if browser_driver_path:
@@ -700,10 +697,11 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
users_config_path = self.ExportUserConfigEdit.text()
if system_config_path:
if self.saveConfigs(
system_config_path,
users_config_path=""
system_config_path, ""
):
msg += f"系统配置文件已导出到: \n'{system_config_path}'\n"
else:
msg += f"系统配置文件导出失败: \n'{system_config_path}'\n"
if users_config_path:
if self.saveConfigs(
"", users_config_path
@@ -712,7 +710,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
if msg:
QMessageBox.information(
self,
"信息 - AutoLibrary",
"提示 - AutoLibrary",
msg
)
+344 -303
View File
@@ -232,8 +232,8 @@
<property name="spacing">
<number>5</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="UsernameKabel">
<item row="2" column="0">
<widget class="QLabel" name="PasswordLabel">
<property name="minimumSize">
<size>
<width>100</width>
@@ -247,7 +247,7 @@
</size>
</property>
<property name="text">
<string>用户名</string>
<string>密码</string>
</property>
</widget>
</item>
@@ -299,25 +299,6 @@
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QLabel" name="PasswordLabel">
<property name="minimumSize">
<size>
<width>100</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>25</height>
</size>
</property>
<property name="text">
<string>密码:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="UsernameEdit">
<property name="minimumSize">
@@ -340,6 +321,25 @@
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="UsernameKabel">
<property name="minimumSize">
<size>
<width>100</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>25</height>
</size>
</property>
<property name="text">
<string>用户名:</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@@ -376,8 +376,65 @@
<property name="spacing">
<number>5</number>
</property>
<item row="4" column="4">
<widget class="QLineEdit" name="SeatIDEdit">
<item row="0" column="1">
<widget class="QLabel" name="DateLabel">
<property name="minimumSize">
<size>
<width>100</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>25</height>
</size>
</property>
<property name="text">
<string>日期:</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QLabel" name="MaxBeginTimeDiffLabel">
<property name="minimumSize">
<size>
<width>100</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>25</height>
</size>
</property>
<property name="text">
<string>最大时长偏差:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="PlaceLabel">
<property name="minimumSize">
<size>
<width>100</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>25</height>
</size>
</property>
<property name="text">
<string>地点:</string>
</property>
</widget>
</item>
<item row="12" column="4">
<widget class="QDoubleSpinBox" name="ExpectDurationSpinBox">
<property name="minimumSize">
<size>
<width>0</width>
@@ -390,6 +447,40 @@
<height>25</height>
</size>
</property>
<property name="toolTip">
<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 name="maximum">
<double>8.000000000000000</double>
</property>
<property name="stepType">
<enum>QAbstractSpinBox::StepType::AdaptiveDecimalStepType</enum>
</property>
</widget>
</item>
<item row="11" column="4">
<widget class="QCheckBox" name="PreferLateEndTimeCheckBox">
<property name="minimumSize">
<size>
<width>0</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>25</height>
</size>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;勾选此项,如果有多个与目标&lt;span style=&quot; font-weight:700; font-style:italic;&quot;&gt;结束时间&lt;/span&gt;相近的可选时间,选择时间最晚项&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>优先选择最晚</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="9" column="1">
@@ -411,11 +502,27 @@
</property>
</widget>
</item>
<item row="1" column="4">
<widget class="QComboBox" name="PlaceComboBox">
<property name="enabled">
<bool>false</bool>
<item row="5" column="1">
<widget class="QLabel" name="BeginTimeLabel">
<property name="minimumSize">
<size>
<width>100</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>25</height>
</size>
</property>
<property name="text">
<string>开始时间:</string>
</property>
</widget>
</item>
<item row="2" column="4">
<widget class="QComboBox" name="FloorComboBox">
<property name="minimumSize">
<size>
<width>0</width>
@@ -430,13 +537,28 @@
</property>
<item>
<property name="text">
<string>图书馆</string>
<string>二层</string>
</property>
</item>
<item>
<property name="text">
<string>三层</string>
</property>
</item>
<item>
<property name="text">
<string>四层</string>
</property>
</item>
<item>
<property name="text">
<string>五层</string>
</property>
</item>
</widget>
</item>
<item row="12" column="1">
<widget class="QLabel" name="ExpectDurationLabel">
<item row="10" column="1">
<widget class="QLabel" name="MaxEndTimeDiffLabel">
<property name="minimumSize">
<size>
<width>100</width>
@@ -450,7 +572,7 @@
</size>
</property>
<property name="text">
<string>期望时长</string>
<string>最大时长偏差</string>
</property>
</widget>
</item>
@@ -487,8 +609,34 @@
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="DateLabel">
<item row="0" column="4">
<widget class="QDateEdit" name="DateEdit">
<property name="minimumSize">
<size>
<width>0</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>25</height>
</size>
</property>
<property name="calendarPopup">
<bool>true</bool>
</property>
<property name="date">
<date>
<year>2025</year>
<month>11</month>
<day>1</day>
</date>
</property>
</widget>
</item>
<item row="12" column="1">
<widget class="QLabel" name="ExpectDurationLabel">
<property name="minimumSize">
<size>
<width>100</width>
@@ -502,7 +650,26 @@
</size>
</property>
<property name="text">
<string>期:</string>
<string>期望时长</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="RoomLabel">
<property name="minimumSize">
<size>
<width>100</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>25</height>
</size>
</property>
<property name="text">
<string>区域:</string>
</property>
</widget>
</item>
@@ -546,6 +713,99 @@
</property>
</widget>
</item>
<item row="13" column="4">
<widget class="QCheckBox" name="SatisfyDurationCheckBox">
<property name="minimumSize">
<size>
<width>0</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>25</height>
</size>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;勾选此项,会优先满足预约时长限制,当座位紧张时可能会导致&lt;span style=&quot; font-weight:700; font-style:italic;&quot;&gt;预约失败&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>优先满足时长要求</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="7" column="4">
<widget class="QSpinBox" name="MaxBeginTimeDiffSpinBox">
<property name="minimumSize">
<size>
<width>100</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>25</height>
</size>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;期望的&lt;span style=&quot; font-weight:700; font-style:italic;&quot;&gt;开始时间&lt;/span&gt;不可用时,按照该偏差范围寻找最近可选时间&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="maximum">
<number>120</number>
</property>
<property name="stepType">
<enum>QAbstractSpinBox::StepType::AdaptiveDecimalStepType</enum>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="FloorLabel">
<property name="minimumSize">
<size>
<width>100</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>25</height>
</size>
</property>
<property name="text">
<string>楼层:</string>
</property>
</widget>
</item>
<item row="1" column="4">
<widget class="QComboBox" name="PlaceComboBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>25</height>
</size>
</property>
<item>
<property name="text">
<string>图书馆</string>
</property>
</item>
</widget>
</item>
<item row="8" column="4">
<widget class="QCheckBox" name="PreferEarlyBeginTimeCheckBox">
<property name="minimumSize">
@@ -571,25 +831,6 @@
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="QLabel" name="MaxEndTimeDiffLabel">
<property name="minimumSize">
<size>
<width>100</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>25</height>
</size>
</property>
<property name="text">
<string>最大时长偏差:</string>
</property>
</widget>
</item>
<item row="10" column="4">
<widget class="QSpinBox" name="MaxEndTimeDiffSpinBox">
<property name="minimumSize">
@@ -610,61 +851,9 @@
<property name="maximum">
<number>120</number>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="RoomLabel">
<property name="minimumSize">
<size>
<width>100</width>
<height>25</height>
</size>
<property name="stepType">
<enum>QAbstractSpinBox::StepType::AdaptiveDecimalStepType</enum>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>25</height>
</size>
</property>
<property name="text">
<string>区域:</string>
</property>
</widget>
</item>
<item row="2" column="4">
<widget class="QComboBox" name="FloorComboBox">
<property name="minimumSize">
<size>
<width>0</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>25</height>
</size>
</property>
<item>
<property name="text">
<string>二层</string>
</property>
</item>
<item>
<property name="text">
<string>三层</string>
</property>
</item>
<item>
<property name="text">
<string>四层</string>
</property>
</item>
<item>
<property name="text">
<string>五层</string>
</property>
</item>
</widget>
</item>
<item row="3" column="4">
@@ -683,91 +872,6 @@
</property>
</widget>
</item>
<item row="7" column="4">
<widget class="QSpinBox" name="MaxBeginTimeDiffSpinBox">
<property name="minimumSize">
<size>
<width>100</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>25</height>
</size>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;期望的&lt;span style=&quot; font-weight:700; font-style:italic;&quot;&gt;开始时间&lt;/span&gt;不可用时,按照该偏差范围寻找最近可选时间&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="maximum">
<number>120</number>
</property>
</widget>
</item>
<item row="11" column="4">
<widget class="QCheckBox" name="PreferLateEndTimeCheckBox">
<property name="minimumSize">
<size>
<width>0</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>25</height>
</size>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;勾选此项,如果有多个与目标&lt;span style=&quot; font-weight:700; font-style:italic;&quot;&gt;结束时间&lt;/span&gt;相近的可选时间,选择时间最晚项&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>优先选择最晚</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLabel" name="BeginTimeLabel">
<property name="minimumSize">
<size>
<width>100</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>25</height>
</size>
</property>
<property name="text">
<string>开始时间:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="FloorLabel">
<property name="minimumSize">
<size>
<width>100</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>25</height>
</size>
</property>
<property name="text">
<string>楼层:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="SeatIDLabel">
<property name="minimumSize">
@@ -787,113 +891,50 @@
</property>
</widget>
</item>
<item row="12" column="4">
<widget class="QDoubleSpinBox" name="ExpectDurationSpinBox">
<property name="minimumSize">
<size>
<width>0</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>25</height>
</size>
</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="maximum">
<double>8.000000000000000</double>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QDateEdit" name="DateEdit">
<property name="minimumSize">
<size>
<width>0</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>25</height>
</size>
</property>
<property name="date">
<date>
<year>2025</year>
<month>11</month>
<day>1</day>
</date>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QLabel" name="MaxBeginTimeDiffLabel">
<property name="minimumSize">
<size>
<width>100</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>25</height>
</size>
</property>
<property name="text">
<string>最大时长偏差:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="PlaceLabel">
<property name="minimumSize">
<size>
<width>100</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>25</height>
</size>
</property>
<property name="text">
<string>地点:</string>
</property>
</widget>
</item>
<item row="13" column="4">
<widget class="QCheckBox" name="SatisfyDurationCheckBox">
<property name="minimumSize">
<size>
<width>0</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>25</height>
</size>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;勾选此项,会优先满足预约时长限制,当座位紧张时可能会导致&lt;span style=&quot; font-weight:700; font-style:italic;&quot;&gt;预约失败&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>优先满足时长要求</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
<item row="4" column="4">
<layout class="QHBoxLayout" name="SeatIDLayout">
<item>
<widget class="QLineEdit" name="SeatIDEdit">
<property name="minimumSize">
<size>
<width>0</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>130</width>
<height>25</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="SelectSeatsButton">
<property name="minimumSize">
<size>
<width>25</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>25</height>
</size>
</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">
<string/>
</property>
<property name="icon">
<iconset theme="edit-find"/>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
@@ -1127,18 +1168,18 @@
<widget class="QLineEdit" name="BrowseBrowserDriverEdit">
<property name="minimumSize">
<size>
<width>265</width>
<width>250</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>260</width>
<width>300</width>
<height>25</height>
</size>
</property>
<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 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>
+35 -22
View File
@@ -80,6 +80,7 @@ class AutoLibWorker(QThread):
self
):
auto_lib = None
try:
if not self.checkTimeAvailable():
self.showTraceSignal.emit(
@@ -98,13 +99,14 @@ class AutoLibWorker(QThread):
ConfigReader(self.__config_paths["system"]),
ConfigReader(self.__config_paths["users"]),
)
auto_lib.close()
self.showTraceSignal.emit("AutoLibrary 运行结束")
except Exception as e:
self.showTraceSignal.emit(
f"AutoLibrary 运行时发生异常 : {e}"
)
finally:
if auto_lib:
auto_lib.close()
self.showTraceSignal.emit("AutoLibrary 运行结束")
self.finishedSignal.emit()
@@ -163,7 +165,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
def closeEvent(
self,
event: QCloseEvent,
event: QCloseEvent
):
if self.__timer and self.__timer.isActive():
@@ -175,7 +177,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
def appendToTextEdit(
self,
text: str,
text: str
):
cursor = self.MessageIOTextEdit.textCursor()
@@ -200,7 +202,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self,
config_button_enabled: bool,
start_button_enabled: bool,
stop_button_enabled: bool,
stop_button_enabled: bool
):
self.ConfigButton.setEnabled(config_button_enabled)
@@ -210,7 +212,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
@Slot()
def showMsg(
self,
msg: str,
msg: str
):
self.appendToTextEdit(f"[{self.__class_name:<12}] >>> : {msg}")
@@ -218,7 +220,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
@Slot()
def showTrace(
self,
msg: str,
msg: str
):
timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
@@ -226,7 +228,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
@Slot()
def pollMsgQueue(
self,
self
):
try:
@@ -239,10 +241,13 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
@Slot(dict)
def onConfigWidgetClosed(
self,
config_paths: dict,
config_paths: dict
):
self.__alConfigWidget = None
if self.__alConfigWidget:
self.__alConfigWidget.configWidgetCloseSingal.disconnect(self.onConfigWidgetClosed)
self.__alConfigWidget.deleteLater()
self.__alConfigWidget = None
self.ConfigButton.setEnabled(True)
self.StartButton.setEnabled(True)
self.StopButton.setEnabled(False)
@@ -250,7 +255,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
@Slot()
def onConfigButtonClicked(
self,
self
):
if self.__alConfigWidget is None:
@@ -268,18 +273,19 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
@Slot()
def onStartButtonClicked(
self,
self
):
self.setControlButtons(False, False, True)
self.__auto_lib_thread = AutoLibWorker(
self.__input_queue,
self.__output_queue,
self.__config_paths,
)
self.__auto_lib_thread.finishedSignal.connect(self.onStopButtonClicked)
self.__auto_lib_thread.showMsgSignal.connect(self.showMsg)
self.__auto_lib_thread.showTraceSignal.connect(self.showTrace)
if self.__auto_lib_thread is None:
self.__auto_lib_thread = AutoLibWorker(
self.__input_queue,
self.__output_queue,
self.__config_paths,
)
self.__auto_lib_thread.finishedSignal.connect(self.onStopButtonClicked)
self.__auto_lib_thread.showMsgSignal.connect(self.showMsg)
self.__auto_lib_thread.showTraceSignal.connect(self.showTrace)
self.__auto_lib_thread.start()
@Slot()
@@ -287,9 +293,16 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self
):
if self.__auto_lib_thread and self.__auto_lib_thread.isRunning():
self.__auto_lib_thread.stop()
if self.__auto_lib_thread:
self.showTrace("正在停止操作......")
self.__auto_lib_thread.stop()
self.__auto_lib_thread.wait()
self.showTrace("操作已停止")
self.__auto_lib_thread.showMsgSignal.disconnect(self.showMsg)
self.__auto_lib_thread.showTraceSignal.disconnect(self.showTrace)
self.__auto_lib_thread.finishedSignal.disconnect(self.onStopButtonClicked)
self.__auto_lib_thread.deleteLater()
self.__auto_lib_thread = None
self.setControlButtons(True, True, False)
@Slot()
+6 -1
View File
@@ -1 +1,6 @@
## Please see in the [manual.html](./document/manual.html)
# AutoLibrary
请访问[AutoLibrary 网站](http://autolibrary.cv)
Please access the [AutoLibrary Website](http://autolibrary.cv)