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

Compare commits

..

18 Commits

Author SHA1 Message Date
github-actions[bot] 3963b3f2e6 chore(release): v1.0.5 [auto release commit] 2026-02-16 07:04:57 +00:00
KenanZhu f2a05809bd ci(batchs): 修复编译脚本中的路径问题 2026-02-16 15:00:35 +08:00
KenanZhu b55a0c06a5 refactor(ALConfigWidget, ALTimerTaskManageWidget): 重构配置和定时器任务管理窗口的配置显式初始化
修改后配置文件的初始化将不再通过 QMessageBox 提示用户,界面将只在初始化失败时显示错误信息。
2026-02-16 14:17:58 +08:00
KenanZhu 2496c4e367 fix(ALMainWindow): 修复配置按钮状态问题 2026-02-16 13:02:40 +08:00
KenanZhu de30559af1 chore(ALTimerTaskManageWidget): 更改信号函数命名 2026-02-16 13:02:01 +08:00
KenanZhu e1c2efc8c0 chore(utils): 配置文件读写器异常改为中文 2026-02-16 13:01:34 +08:00
KenanZhu 26a70cdceb ci(batchs): 修复 *.sh 编译脚本中项目路径问题 2026-02-11 20:17:04 +08:00
KenanZhu ce14be2555 chore(*): 重构项目文件目录结构
- 将 src/gui 目录下的 Qt 资源文件移动到 src/gui/resources 目录下
- 将 src/gui 目录下的 Qt UI 设计文件移动到 src/gui/resources/ui 目录下
- 将 src/gui/icons 目录下的图标文件移动到 src/gui/resources/icons 目录下
- 将 src/gui/translators 目录下移动到 src/gui/resources/translators 目录下
- 将 src/gui/configs 目录移动到 templates 目录下
- 将 document, driver, model 目录重命名为 manuals, drivers, models
- 由于上述目录移动和重命名,相应的更改了代码和批处理脚本中的文件路径
2026-02-11 20:00:51 +08:00
KenanZhu eda16f01f1 refactor(gui): chore(gui): 对部分界面类进行重构,将 ALSeatMapView 提取到单独文件,将 ALSeatMapWidget 重替换为 ALSeatMapSelectDialog
: 对文件名进行重命名,以更贴近各自功能,ALTimerTaskWidget 重命名为 ALTimerTaskManageWidget;ALAddTimerTaskDialog 重命名为 ALTimerTaskAddDialog
2026-02-03 15:03:33 +08:00
KenanZhu 22f806bfb0 chore(*): 更新有关帮助手册的链接 2026-01-30 22:10:00 +08:00
KenanZhu d26852eaaf chore(*): 更新网站地址为 www.autolibrary.top 2026-01-30 22:04:29 +08:00
KenanZhu 2ffe620532 optimize(AutoLib): 优化图书馆登录页面加载超时处理逻辑 2026-01-26 16:11:26 +08:00
KenanZhu fe42d3cd98 fix(AutoLib): 修复浏览器驱动初始化的异常控制 2026-01-26 16:10:15 +08:00
KenanZhu 0795939aa3 docs(readme): 交换使用方法与注意事项的顺序 2026-01-23 17:57:41 +08:00
KenanZhu 8b6baf9b6a refactor(ALMainWindow): 重构主窗口类的消息队列能力,修改为直接从 MsgBase 继承 2026-01-20 17:45:32 +08:00
KenanZhu 7098d7075f refactor(ALMainWorkers): 重构主工作线程的父类初始化方式 2026-01-20 17:43:52 +08:00
KenanZhu be3942ea2f optimize(MsgBase): 优化消息队列能力基类,增加小数秒精度时间戳,移除无用方法 '_inputMsg' 2026-01-20 17:41:34 +08:00
KenanZhu 7e3a089e21 refactor(gui.ALSeatMapWidget): 重构座位选图控件
将座位图提取为 ALSeatMapView 类,并添加缩放限制
2026-01-18 13:50:48 +08:00
48 changed files with 656 additions and 602 deletions
+14 -14
View File
@@ -94,13 +94,13 @@ jobs:
exit 1
}
if (-not (Test-Path "model")) {
New-Item -ItemType Directory -Path "model" | Out-Null
Write-Host "✓ Created model directory"
if (-not (Test-Path "models")) {
New-Item -ItemType Directory -Path "models" | Out-Null
Write-Host "✓ Created models directory"
}
$onnxSource = Join-Path $ddddocrPath "common.onnx"
$onnxDest = "model/common.onnx"
$onnxDest = "models/common.onnx"
if (Test-Path $onnxSource) {
Copy-Item $onnxSource $onnxDest -Force
Write-Host "✓ Copied ONNX model from: $onnxSource"
@@ -119,18 +119,18 @@ jobs:
}
shell: pwsh
- name: Compile Qt UI files
run: |
cd src/gui/batchs
./compile_ui.bat
shell: cmd
- name: Compile Qt Resource files
run: |
cd src/gui/batchs
cd batchs
./compile_rc.bat
shell: cmd
- name: Compile Qt UI files
run: |
cd batchs
./compile_ui.bat
shell: cmd
- name: Generate 'Main.spec'
run: |
$version = "${{ steps.get_version.outputs.VERSION }}"
@@ -148,8 +148,8 @@ jobs:
" pathex=[],"
" binaries=[],"
" datas=["
" ('model\\common.onnx', 'ddddocr'),"
" ('src\\gui\\icons\\AutoLibrary_32x32.ico', 'gui\\icons'),"
" ('models\\common.onnx', 'ddddocr'),"
" ('src\\gui\\resources\\icons\\AutoLibrary_32x32.ico', 'gui\\resources\\icons'),"
" ],"
" hiddenimports=[],"
" hookspath=[],"
@@ -180,7 +180,7 @@ jobs:
" target_arch=None,"
" codesign_identity=None,"
" entitlements_file=None,"
" icon=['src\\gui\\icons\\AutoLibrary_32x32.ico'],"
" icon=['src\\gui\\resources\\icons\\AutoLibrary_32x32.ico'],"
")"
)
$specLines | Out-File -FilePath "Main.spec" -Encoding UTF8
+1 -1
View File
@@ -135,7 +135,7 @@ jobs:
4. 运行 `AutoLibrary-${{ needs.build.outputs.version }}.exe` (首次运行会初始化配置文件)
5. 按照提示操作即可
更多详情请访问 [AutoLibrary 网站](http://autolibrary.cv) 和查看 [帮助手册](https://autolibrary.cv/docs/manual_lists.html)
更多详情请访问 [AutoLibrary 网站](http://www.autolibrary.top) 和查看 [帮助手册](https://www.autolibrary.top/manuals)
---
**完整更新日志见下方自动生成的 Release Notes**
+11 -11
View File
@@ -6,16 +6,16 @@
__pycache__/
build/
dist/
model/*.onnx
driver/*.exe
src/gui/configs/*.json
src/gui/translators/qtbase_zh_CN.qm
src/gui/AutoLibraryResources.py
src/gui/AutoLibraryResource.py
src/gui/Ui_ALMainWindow.py
src/gui/Ui_ALConfigWidget.py
src/gui/Ui_ALTimerTaskWidget.py
src/gui/Ui_ALAddTimerTaskDialog.py
src/gui/Ui_ALAboutDialog.py
models/*.*
drivers/*.*
!models/*.md
!drivers/*.md
!templates/*.md
!templates/configs/*.md
src/gui/resources/ui/Ui_*.py
src/gui/resources/translators/qtbase_zh_CN.qm
src/gui/resources/ALResource.py
Main.spec
@@ -3,6 +3,7 @@ chcp 65001 >nul
setlocal enabledelayedexpansion
cd /d "%~dp0.."
cd src/gui/resources
echo [AutoLibrary compile] 检查翻译文件...
if exist translators (
@@ -18,12 +19,11 @@ if exist translators (
pyside6-lrelease "%%f"
if !errorlevel! equ 0 (
echo [AutoLibrary compile] 翻译文件 "%%f" 编译成功,输出文件: "!qm_filename!"
echo [AutoLibrary compile] 翻译文件 "%%f" 编译成功,输出文件: "!qm_filename!"
) else (
echo [AutoLibrary compile] 翻译文件 "%%f" 编译失败
echo [AutoLibrary compile] 翻译文件 "%%f" 编译失败
)
)
echo.
) else (
echo [AutoLibrary compile] 未找到任何 .ts 翻译文件
)
@@ -52,11 +52,10 @@ for %%f in (*.qrc) do (
pyside6-rcc "%%f" -o "!output_file!"
if !errorlevel! equ 0 (
echo [AutoLibrary compile] 文件 "%%f" 编译成功,输出文件: "!output_file!"
echo [AutoLibrary compile] 文件 "%%f" 编译成功,输出文件: "!output_file!"
) else (
echo [AutoLibrary compile] 文件 "%%f" 编译失败
echo [AutoLibrary compile] 文件 "%%f" 编译失败
)
echo.
)
echo [AutoLibrary compile] 所有操作完成。
+7 -11
View File
@@ -1,8 +1,9 @@
#!/bin/bash
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PARENT_DIR="$(dirname "$SCRIPT_DIR")"
cd "$PARENT_DIR"
PRJECT_DIR="$SCRIPT_DIR/.."
cd "$PRJECT_DIR/src/gui/resources"
echo "[AutoLibrary compile] 检查翻译文件..."
if [ -d "translators" ]; then
@@ -10,7 +11,6 @@ if [ -d "translators" ]; then
ts_files=(*.ts)
ts_count=${#ts_files[@]}
# 如果第一个元素是"*.ts"(表示没有匹配),则数量为0
if [ "$ts_count" -eq 1 ] && [ "${ts_files[0]}" = "*.ts" ]; then
ts_count=0
fi
@@ -23,12 +23,11 @@ if [ -d "translators" ]; then
echo "[AutoLibrary compile] 正在编译翻译文件: \"$file\" -> \"$qm_file\""
if pyside6-lrelease "$file"; then
echo "[AutoLibrary compile] 翻译文件 \"$file\" 编译成功,输出文件: \"$qm_file\""
echo "[AutoLibrary compile] 翻译文件 \"$file\" 编译成功,输出文件: \"$qm_file\""
else
echo "[AutoLibrary compile] 翻译文件 \"$file\" 编译失败"
echo "[AutoLibrary compile] 翻译文件 \"$file\" 编译失败"
fi
done
echo
else
echo "[AutoLibrary compile] 未找到任何 .ts 翻译文件"
fi
@@ -36,7 +35,6 @@ if [ -d "translators" ]; then
else
echo "[AutoLibrary compile] 未找到 translators 目录"
fi
echo
file_count=$(ls *.qrc 2>/dev/null | wc -l)
@@ -46,7 +44,6 @@ if [ $file_count -eq 0 ]; then
fi
echo "[AutoLibrary compile] 找到 $file_count 个 .qrc 文件,开始编译..."
echo
for file in *.qrc; do
base_name=$(basename "$file" .qrc)
@@ -54,11 +51,10 @@ for file in *.qrc; do
echo "[AutoLibrary compile] 正在编译: \"$file\" -> \"$output_file\""
if pyside6-rcc "$file" -o "$output_file"; then
echo "[AutoLibrary compile] 文件 \"$file\" 编译成功,输出文件: \"$output_file\""
echo "[AutoLibrary compile] 文件 \"$file\" 编译成功,输出文件: \"$output_file\""
else
echo "[AutoLibrary compile] 文件 \"$file\" 编译失败"
echo "[AutoLibrary compile] 文件 \"$file\" 编译失败"
fi
echo
done
echo "[AutoLibrary compile] 所有操作完成。"
@@ -3,6 +3,7 @@ chcp 65001 >nul
setlocal enabledelayedexpansion
cd /d "%~dp0.."
cd src/gui/resources/ui
set count=0
for %%f in (*.ui) do set /a count+=1
@@ -23,11 +24,10 @@ for %%f in (*.ui) do (
pyside6-uic "%%f" -o "!output_file!"
if !errorlevel! equ 0 (
echo [AutoLibrary compile] 文件 "%%f" 编译成功,输出文件: "!output_file!"
echo [AutoLibrary compile] 文件 "%%f" 编译成功,输出文件: "!output_file!"
) else (
echo [AutoLibrary compile] 文件 "%%f" 编译失败
echo [AutoLibrary compile] 文件 "%%f" 编译失败
)
echo.
)
echo [AutoLibrary compile] 所有操作完成。
+5 -6
View File
@@ -1,8 +1,9 @@
#!/bin/bash
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PARENT_DIR="$(dirname "$SCRIPT_DIR")"
cd "$PARENT_DIR"
PRJECT_DIR="$SCRIPT_DIR/.."
cd "$PRJECT_DIR/src/gui/resources/ui"
file_count=$(ls *.ui 2>/dev/null | wc -l)
@@ -12,7 +13,6 @@ if [ $file_count -eq 0 ]; then
fi
echo "[AutoLibrary compile] 找到 $file_count 个 .ui 文件,开始编译..."
echo
for file in *.ui; do
base_name=$(basename "$file" .ui)
@@ -20,11 +20,10 @@ for file in *.ui; do
echo "[AutoLibrary compile] 正在编译: \"$file\" -> \"$output_file\""
if pyside6-uic "$file" -o "$output_file"; then
echo "[AutoLibrary compile] 文件 \"$file\" 编译成功,输出文件: \"$output_file\""
echo "[AutoLibrary compile] 文件 \"$file\" 编译成功,输出文件: \"$output_file\""
else
echo "[AutoLibrary compile] 文件 \"$file\" 编译失败"
echo "[AutoLibrary compile] 文件 \"$file\" 编译失败"
fi
echo
done
echo "[AutoLibrary compile] 所有操作完成。"
+1
View File
@@ -0,0 +1 @@
This folder is used to store the batch scripts.
-1
View File
@@ -1 +0,0 @@
For more infomation, please visit our website: https://www.autolibrary.cv
-1
View File
@@ -1 +0,0 @@
This folder is used to store the browser driver using by selenium.
+1
View File
@@ -0,0 +1 @@
This folder is used to store the browser drivers using by selenium.
+3
View File
@@ -0,0 +1,3 @@
This folder is used to store the manuals.
Our manuals are available at https://www.autolibrary.top/manuals
-1
View File
@@ -1 +0,0 @@
This folder is used to store the model using by ddddocr.
+1
View File
@@ -0,0 +1 @@
This folder is used to store the models using by ddddocr.
+38 -38
View File
@@ -2,7 +2,7 @@
# AutoLibrary
---
![AutoLibrary Logo](./src/gui/icons/AutoLibrary_128x128.ico)
![AutoLibrary Logo](./src/gui/resources/icons/AutoLibrary_128x128.ico)
[![GitHub stars](https://img.shields.io/github/stars/KenanZhu/AutoLibrary.svg?style=social&label=Star)](https://github.com/KenanZhu/AutoLibrary)
![License](https://img.shields.io/github/license/KenanZhu/AutoLibrary)
@@ -10,7 +10,7 @@
[![Release](https://img.shields.io/github/v/release/KenanZhu/AutoLibrary)](https://github.com/KenanZhu/AutoLibrary/releases)
![Downloads](https://img.shields.io/github/downloads/KenanZhu/AutoLibrary/total)
了解更多请访问 [_AutoLibrary 网站_](http://autolibrary.cv)
了解更多请访问 [_AutoLibrary 网站_](http://www.autolibrary.top)
---
@@ -22,7 +22,42 @@
4. 批量操作 - 支持同时预约多个用户,可以指定当前需要跳过的用户,并将用户分成多个组
5. 定时任务 - 使用内置定时任务管理,添加定时任务,指定时间后按当前预约信息自动运行
*1,2,3 的具体操作方法和注意事项请访问我们的 [帮助手册](https://autolibrary.cv/docs/manual_lists.html)*
*1,2,3 的具体操作方法和注意事项请访问我们的 [帮助手册](https://www.autolibrary.top/manuals)*
### 如何使用
1. 下载最新版本的 [AutoLibrary 压缩包](https://github.com/KenanZhu/AutoLibrary/releases)。
2. 解压下载的文件到任意目录。
3. 下载对应浏览器的驱动文件,并在配置界面的运行配置选项卡对应位置选择你下载好的浏览器驱动
4. 运行 `AutoLibrary.exe` 文件。
5. 按照提示操作即可。
*注意 1*: 关于浏览器驱动的下载和其它相关问题,请参考我们的 [帮助手册](https://www.autolibrary.top/manuals) 中对应软件版本的内容。
#### 平台支持 & 编译步骤
本工具目前仅支持 Windows 平台,由于使用 PySide6 库开发,理论上是可以自行编译并在 Linux 和 macOS 上运行,这里提供简单的编译步骤:
1. 确保系统安装了 Python 3.13 版本 (推荐,过低或高版本会导致兼容问题)。
2. 安装 pyside6 selenium ddddocr 库,命令为 `pip install pyside6 selenium ddddocr`
3.`batchs` 目录下运行 `compile_ui.bat` linux 和 macOS 系统使用 `compile_ui.sh`) 文件来编译 Qt 的 UI 文件。
4. 在上一步相同目录内运行 `compile_rc.bat` linux 和 macOS 系统使用 `compile_rc.sh` 文件来编译 Qt 的资源文件。
5. 待上述步骤完成后,运行 `src/Main.py` 文件即可。
*注意 1*:如果 python 使用的是虚拟环境,请在虚拟环境安装依赖后,在激活的虚拟环境终端中使用 `cd src/gui/batchs` 命令切换到 `batchs` 目录下,再运行编译脚本。否则会提示缺少必要的 Qt PySide 依赖库。
*注意 2*:由于 ddddocr 的代码版本问题,其中 `__init__.py` 文件中的函数 `def classification(self, img: bytes):` 中的 `image.resize` 方法传入了不符合当前 pillow 版本的 `resample` 参数 `Image.ANTIALIAS`,该重采样常量已经在 10.0.0 版中删除 [1](@ref)。请将 `image.resize` 方法中的参数替换为 `resample=Image.Resampling.LANCZOS`,具体函数如下:
```python
def classification(self, img: bytes):
image = Image.open(io.BytesIO(img))
image = image.resize((int(image.size[0]*(64/image.size[1])), 64), Image.ANTIALIAS).convert('L')
^^^^^
请将上述参数替换为 `Image.Resampling.LANCZOS`
...
```
[1](@ref)[pillow 中已经删除或已经弃用的常量](https://pillow.ac.cn/en/stable/deprecations.html#constants)
### 注意事项
@@ -43,41 +78,6 @@
定时任务会在指定的时间自动运行,运行时会根据当前预约信息进行操作。一般情况下不建议设置两个运行开始时间比较接近的定时任务,否则后一个任务会等待前一个任务完成后才会运行,按照队列的顺序执行。
### 如何使用
1. 下载最新版本的 [AutoLibrary 压缩包](https://github.com/KenanZhu/AutoLibrary/releases)。
2. 解压下载的文件到任意目录。
3. 下载对应浏览器的驱动文件,并在配置界面的运行配置选项卡对应位置选择你下载好的浏览器驱动
4. 运行 `AutoLibrary.exe` 文件。
5. 按照提示操作即可。
*注意 1*: 关于浏览器驱动的下载和其它相关问题,请参考我们的 [帮助手册](https://autolibrary.cv/docs/manual_lists.html) 中对应软件版本的内容。
#### 平台支持 & 编译步骤
本工具目前仅支持 Windows 平台,由于使用 PySide6 库开发,理论上是可以自行编译并在 Linux 和 macOS 上运行,这里提供简单的编译步骤:
1. 确保系统安装了 Python 3.13 版本 (推荐,过低或高版本会导致兼容问题)。
2. 安装 pyside6 selenium ddddocr 库,命令为 `pip install pyside6 selenium ddddocr`
3.`src/gui/batchs` 目录下运行 `compile_ui.bat` linux 和 macOS 系统使用 `compile_ui.sh`) 文件来编译 Qt 的 UI 文件。
4. 在上一步相同目录内运行 `compile_rc.bat` linux 和 macOS 系统使用 `compile_rc.sh` 文件来编译 Qt 的资源文件。
5. 待上述步骤完成后,运行 `src/Main.py` 文件即可。
*注意 1*:如果 python 使用的是虚拟环境,请在虚拟环境安装依赖后,在激活的虚拟环境终端中使用 `cd src/gui/batchs` 命令切换到 `batchs` 目录下,再运行编译脚本。否则会提示缺少必要的 Qt PySide 依赖库。
*注意 2*:由于 ddddocr 的代码版本问题,其中 `__init__.py` 文件中的函数 `def classification(self, img: bytes):` 中的 `image.resize` 方法传入了不符合当前 pillow 版本的 `resample` 参数 `Image.ANTIALIAS`,该重采样常量已经在 10.0.0 版中删除 [1](@ref)。请将 `image.resize` 方法中的参数替换为 `resample=Image.Resampling.LANCZOS`,具体函数如下:
```python
def classification(self, img: bytes):
image = Image.open(io.BytesIO(img))
image = image.resize((int(image.size[0]*(64/image.size[1])), 64), Image.ANTIALIAS).convert('L')
^^^^^
请将上述参数替换为 `Image.Resampling.LANCZOS`
...
```
[1](@ref)[pillow 中已经删除或已经弃用的常量](https://pillow.ac.cn/en/stable/deprecations.html#constants)
### Q&A
#### 为什么开发这个工具?
+1 -1
View File
@@ -13,7 +13,7 @@ from PySide6.QtCore import QTranslator
from PySide6.QtWidgets import QApplication
from gui.ALMainWindow import ALMainWindow
from gui import AutoLibraryResource
from gui.resources import ALResource
def main():
+2 -14
View File
@@ -7,8 +7,8 @@ This software is provided "as is", without any warranty of any kind.
You may use, modify, and distribute this file under the terms of the MIT License.
See the LICENSE file for details.
"""
import time
import queue
import datetime
class MsgBase:
@@ -53,7 +53,7 @@ class MsgBase:
msg: str
):
timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
self._output_queue.put(f"{timestamp}-[{self._class_name:<15}] : {msg}")
@@ -67,15 +67,3 @@ class MsgBase:
return msg
except queue.Empty:
return None
def _inputMsg(
self,
timeout: float = 1.0
) -> bool:
try:
self._input_queue.get(timeout=timeout)
return True
except queue.Empty:
return False
+10 -8
View File
@@ -7,11 +7,10 @@ This software is provided "as is", without any warranty of any kind.
You may use, modify, and distribute this file under the terms of the MIT License.
See the LICENSE file for details.
"""
import sys
import platform
from PySide6.QtGui import (
QIcon
QIcon, QFont
)
from PySide6.QtWidgets import (
QDialog, QApplication
@@ -23,9 +22,9 @@ from PySide6.QtCore import (
from gui.ALVersionInfo import (
AL_VERSION, AL_COMMIT_SHA, AL_COMMIT_DATE, AL_BUILD_DATE
)
from gui.Ui_ALAboutDialog import Ui_ALAboutDialog
from gui.resources.ui.Ui_ALAboutDialog import Ui_ALAboutDialog
from gui import AutoLibraryResource
from gui.resources import ALResource
class ALAboutDialog(QDialog, Ui_ALAboutDialog):
@@ -47,8 +46,11 @@ class ALAboutDialog(QDialog, Ui_ALAboutDialog):
self.LogoIconLabel.setPixmap(QIcon(":/res/icon/icons/AutoLibrary_32x32.ico").pixmap(48, 48))
info_text = self.generateAboutText()
self.AboutInfoEdit.setHtml(info_text)
self.AboutInfoEdit.setTextInteractionFlags(Qt.TextBrowserInteraction)
self.AboutInfoBrowser.setHtml(info_text)
browser_font = self.AboutInfoBrowser.font()
browser_font.setFamily("Courier New")
self.AboutInfoBrowser.setFont(browser_font)
self.AboutInfoBrowser.setTextInteractionFlags(Qt.TextBrowserInteraction)
def connectSignals(
@@ -81,7 +83,7 @@ System architecture: {os_info['architecture']}<br>
<h4>Project Information:</h4>
License: MIT License<br>
Project repository: <a href="https://www.github.com/KenanZhu/AutoLibrary" style="text-decoration: none;">https://www.github.com/KenanZhu/AutoLibrary</a><br>
Project website: <a href="https://www.autolibrary.cv/" style="text-decoration: none;">https://www.autolibrary.cv/</a><br>
Project website: <a href="https://www.autolibrary.top" style="text-decoration: none;">https://www.autolibrary.top</a><br>
<h4>Author Information:</h4>
Developer: KenanZhu<br>
@@ -138,7 +140,7 @@ GitHub: <a href="https://www.github.com/KenanZhu" style="text-decoration: none;"
self
):
about_text = self.AboutInfoEdit.toPlainText()
about_text = self.AboutInfoBrowser.toPlainText()
clipboard = QApplication.clipboard()
clipboard.setText(about_text)
original_text = self.CopyButton.text()
+32 -53
View File
@@ -14,18 +14,17 @@ from PySide6.QtCore import (
Qt, Signal, Slot, QTime, QDate, QDir, QFileInfo
)
from PySide6.QtWidgets import (
QWidget, QLineEdit, QMessageBox, QFileDialog,
QDialog, QWidget, QLineEdit, QMessageBox, QFileDialog,
QTreeWidgetItem, QMenu, QInputDialog
)
from PySide6.QtGui import (
QCloseEvent, QAction
)
from gui.Ui_ALConfigWidget import Ui_ALConfigWidget
from gui.ALSeatMapWidget import ALSeatMapWidget
from gui.ALSeatMapTable import seats_maps
from gui.ALUserTreeWidget import TreeItemType
from gui.ALUserTreeWidget import ALUserTreeWidget
from gui.resources.ui.Ui_ALConfigWidget import Ui_ALConfigWidget
from gui.ALSeatMapSelectDialog import ALSeatMapSelectDialog
from gui.ALSeatMapTable import ALSeatMapTable
from gui.ALUserTreeWidget import ALUserTreeWidget, ALUserTreeItemType
from utils.ConfigReader import ConfigReader
from utils.ConfigWriter import ConfigWriter
@@ -47,7 +46,6 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
super().__init__(parent)
self.__config_paths = config_paths
self.__config_data = {"run": {}, "user": {}}
self.__seat_map_widget = None
self.setupUi(self)
self.modifyUi()
@@ -196,7 +194,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
which: str
) -> bool:
msg = ""
msg = "" # no use for now
is_success = True
if which == "run":
run_config_path = self.__config_paths[which]
@@ -224,12 +222,6 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
self.__config_data[which] = self.loadUserConfig(user_config_path)
if self.__config_data[which] is None:
is_success = False
if msg:
QMessageBox.information(
self,
"提示 - AutoLibrary",
f"配置文件初始化完成: \n{msg}"
)
return is_success
@@ -625,12 +617,12 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
try:
if "groups" in user_config_data:
for group_config in user_config_data["groups"]:
group_item = QTreeWidgetItem(self.UserTreeWidget, TreeItemType.GROUP.value)
group_item = QTreeWidgetItem(self.UserTreeWidget, ALUserTreeItemType.GROUP.value)
group_item.setText(0, group_config["name"])
group_item.setFlags(group_item.flags() | Qt.ItemIsEditable)
group_item.setCheckState(1, Qt.Checked if group_config.get("enabled", True) else Qt.Unchecked)
for user_config in group_config["users"]:
user_item = QTreeWidgetItem(group_item, TreeItemType.USER.value)
user_item = QTreeWidgetItem(group_item, ALUserTreeItemType.USER.value)
user_item.setText(0, user_config["username"])
user_item.setText(1, "" if user_config.get("enabled", True) else "跳过")
user_item.setData(0, Qt.UserRole, user_config)
@@ -647,7 +639,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
) -> QTreeWidgetItem:
self.UserTreeWidget.itemChanged.disconnect(self.onUserTreeWidgetItemChanged)
group_item = QTreeWidgetItem(self.UserTreeWidget, TreeItemType.GROUP.value)
group_item = QTreeWidgetItem(self.UserTreeWidget, ALUserTreeItemType.GROUP.value)
if not group_name:
group_name = f"新分组-{self.UserTreeWidget.topLevelItemCount()}"
group_item.setText(0, group_name)
@@ -667,7 +659,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
current_item = self.UserTreeWidget.currentItem()
if current_item is None:
group_item = self.addGroup()
if group_item.type() == TreeItemType.USER.value:
if group_item.type() == ALUserTreeItemType.USER.value:
group_item = group_item.parent()
if group_item.checkState(1) == Qt.CheckState.Unchecked:
return None
@@ -701,7 +693,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
}
}
self.UserTreeWidget.itemChanged.disconnect(self.onUserTreeWidgetItemChanged)
user_item = QTreeWidgetItem(group_item, TreeItemType.USER.value)
user_item = QTreeWidgetItem(group_item, ALUserTreeItemType.USER.value)
user_item.setText(0, new_user["username"])
user_item.setText(1, "")
user_item.setData(0, Qt.UserRole, new_user)
@@ -720,7 +712,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
if user_item is None:
return
if user_item.type() != TreeItemType.USER.value:
if user_item.type() != ALUserTreeItemType.USER.value:
return
parent_item = user_item.parent()
index = parent_item.indexOfChild(user_item)
@@ -736,7 +728,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
if group_item is None:
return
if group_item.type() != TreeItemType.GROUP.value:
if group_item.type() != ALUserTreeItemType.GROUP.value:
return
index = self.UserTreeWidget.indexOfTopLevelItem(group_item)
self.UserTreeWidget.takeTopLevelItem(index)
@@ -761,7 +753,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
if not ok or not new_name:
return
item.setText(0, new_name)
if item.type() == TreeItemType.GROUP.value:
if item.type() == ALUserTreeItemType.GROUP.value:
item.setText(0, new_name)
else:
user = item.data(0, Qt.UserRole)
@@ -792,20 +784,6 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
self.RoomComboBox.addItems(self.__floor_room_map[floor])
self.RoomComboBox.setCurrentIndex(0)
@Slot()
def onSeatMapWidgetClosed(
self,
selected_seats: list[str]
):
self.__seat_map_widget.seatMapWidgetClosed.disconnect(self.onSeatMapWidgetClosed)
self.__seat_map_widget.deleteLater()
self.__seat_map_widget = None
if len(selected_seats) == 0:
self.SeatIDEdit.clear() # no selected seat, we clear the edit
return
self.SeatIDEdit.setText(",".join(selected_seats))
@Slot()
def onSelectSeatsButtonClicked(
self
@@ -815,18 +793,19 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
room = self.RoomComboBox.currentText()
floor_idx = self.__floor_rmap[floor]
room_idx = self.__room_rmap[room]
if self.__seat_map_widget is None:
self.__seat_map_widget = ALSeatMapWidget(
self,
floor,
room,
seats_maps[floor_idx][room_idx]
)
self.__seat_map_widget.seatMapWidgetClosed.connect(self.onSeatMapWidgetClosed)
self.__seat_map_widget.show()
self.__seat_map_widget.raise_()
self.__seat_map_widget.activateWindow()
self.__seat_map_widget.selectSeats(self.SeatIDEdit.text().split(","))
dialog = ALSeatMapSelectDialog(
self,
floor,
room,
ALSeatMapTable[floor_idx][room_idx]
)
dialog.selectSeats(self.SeatIDEdit.text().split(","))
if dialog.exec() == QDialog.DialogCode.Accepted:
selected_seats = dialog.getSelectedSeats()
if len(selected_seats) == 0:
self.SeatIDEdit.clear()
return
self.SeatIDEdit.setText(",".join(dialog.getSelectedSeats()))
@Slot()
def onUserTreeWidgetCurrentItemChanged(
@@ -838,7 +817,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
# cant effectively update the data of each user, due to the
# possiblity of frequency edit. we just let the QListWidget
# help us.
if previous and previous.type() == TreeItemType.USER.value:
if previous and previous.type() == ALUserTreeItemType.USER.value:
user = self.collectUserFromUserInfoWidget()
if user:
self.UsernameEdit.textEdited.disconnect()
@@ -849,7 +828,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
if current is None:
self.initilizeUserInfoWidget()
return
if current.type() == TreeItemType.USER.value:
if current.type() == ALUserTreeItemType.USER.value:
user = current.data(0, Qt.UserRole)
if user:
self.setUserToWidget(user)
@@ -868,7 +847,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
return
if column != 1:
return
if item.type() == TreeItemType.GROUP.value:
if item.type() == ALUserTreeItemType.GROUP.value:
is_checked = item.checkState(1) == Qt.CheckState.Checked
for i in range(item.childCount()):
child = item.child(i)
@@ -933,7 +912,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
menu = QMenu(self.UserTreeWidget)
if current_item is None:
self.showTreeMenu(menu)
elif current_item.type() == TreeItemType.GROUP.value:
elif current_item.type() == ALUserTreeItemType.GROUP.value:
self.showGroupMenu(menu, current_item)
else:
self.showUserMenu(menu, current_item)
@@ -1115,7 +1094,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
):
current_item = self.UserTreeWidget.currentItem()
if current_item and current_item.type() == TreeItemType.USER.value:
if current_item and current_item.type() == ALUserTreeItemType.USER.value:
self.UserTreeWidget.setCurrentItem(None)
if self.saveConfigs(
self.__config_paths["run"],
+22 -39
View File
@@ -21,16 +21,18 @@ from PySide6.QtGui import (
QTextCursor, QCloseEvent, QFont, QIcon, QDesktopServices
)
from gui.Ui_ALMainWindow import Ui_ALMainWindow
from base.MsgBase import MsgBase
from gui.resources.ui.Ui_ALMainWindow import Ui_ALMainWindow
from gui.ALConfigWidget import ALConfigWidget
from gui.ALTimerTaskWidget import ALTimerTaskWidget
from gui.ALTimerTaskManageWidget import ALTimerTaskManageWidget
from gui.ALAboutDialog import ALAboutDialog
from gui.ALMainWorkers import TimerTaskWorker, AutoLibWorker
from gui import AutoLibraryResource
from gui.resources import ALResource
class ALMainWindow(QMainWindow, Ui_ALMainWindow):
class ALMainWindow(MsgBase, QMainWindow, Ui_ALMainWindow):
timerTaskIsRunning = Signal(dict)
timerTaskIsExecuted = Signal(dict)
@@ -40,10 +42,8 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self
):
super().__init__()
self.__class_name = self.__class__.__name__
self.__input_queue = queue.Queue()
self.__output_queue = queue.Queue()
MsgBase.__init__(self, queue.Queue(), queue.Queue())
QMainWindow.__init__(self)
self.__timer_task_queue = queue.Queue()
script_path = sys.executable
script_dir = QFileInfo(script_path).absoluteDir()
@@ -77,12 +77,12 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self.AboutAction.triggered.connect(self.onAboutActionTriggered)
# initialize timer task widget, but not show it
self.__alTimerTaskWidget = ALTimerTaskWidget(self, self.__config_paths["timer_task"])
self.__alTimerTaskWidget = ALTimerTaskManageWidget(self, self.__config_paths["timer_task"])
self.timerTaskIsRunning.connect(self.__alTimerTaskWidget.onTimerTaskIsRunning)
self.timerTaskIsExecuted.connect(self.__alTimerTaskWidget.onTimerTaskIsExecuted)
self.timerTaskIsError.connect(self.__alTimerTaskWidget.onTimerTaskIsError)
self.__alTimerTaskWidget.timerTaskIsReady.connect(self.onTimerTaskIsReady)
self.__alTimerTaskWidget.timerTaskWidgetClosed.connect(self.onTimerTaskWidgetClosed)
self.__alTimerTaskWidget.timerTaskManageWidgetClosed.connect(self.onTimerTaskWidgetClosed)
self.__alTimerTaskWidget.setWindowFlags(Qt.WindowType.Window|Qt.WindowType.WindowCloseButtonHint)
@@ -98,7 +98,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self
):
url = QUrl("https://www.autolibrary.cv/docs/manual_lists.html")
url = QUrl("https://www.autolibrary.top/manuals")
QDesktopServices.openUrl(url)
@@ -226,7 +226,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self.timerTaskIsRunning.emit(timer_task)
self.__timer_task_timer.stop()
self.__is_running_timer_task = True
self.setControlButtons(True, True, False)
self.setControlButtons(None, True, False)
if not timer_task["silent"]:
self.TrayIcon.showMessage(
"定时任务 - AutoLibrary",
@@ -237,8 +237,8 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self.showNormal()
self.__current_timer_task_thread = TimerTaskWorker(
timer_task,
self.__input_queue,
self.__output_queue,
self._input_queue,
self._output_queue,
self.__config_paths
)
self.__current_timer_task_thread.TimerTaskWorkerIsFinished.connect(self.onTimerTaskFinished)
@@ -263,23 +263,6 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
if start_button_enabled is not None:
self.StartButton.setEnabled(start_button_enabled)
@Slot()
def showMsg(
self,
msg: str
):
self.__output_queue.put(f"[{self.__class_name:<15}] >>> : {msg}")
@Slot()
def showTrace(
self,
msg: str
):
timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
self.__output_queue.put(f"{timestamp}-[{self.__class_name:<15}] : {msg}")
@Slot()
def pollMsgQueue(
self
@@ -287,7 +270,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
try:
while True:
msg = self.__output_queue.get_nowait()
msg = self._output_queue.get_nowait()
self.appendToTextEdit(msg)
except queue.Empty:
pass
@@ -341,7 +324,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
QSystemTrayIcon.MessageIcon.Information,
1000
)
self.showTrace(
self._showTrace(
f"定时任务 {timer_task['name']} 执行{'失败' if is_error else '完成'}, uuid: {timer_task['task_uuid']}"
)
if not is_error:
@@ -383,8 +366,8 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self.setControlButtons(None, True, False)
if self.__auto_lib_thread is None:
self.__auto_lib_thread = AutoLibWorker(
self.__input_queue,
self.__output_queue,
self._input_queue,
self._output_queue,
self.__config_paths
)
self.__auto_lib_thread.AutoLibWorkerIsFinished.connect(self.onStopButtonClicked)
@@ -397,9 +380,9 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
):
if self.__auto_lib_thread:
self.showTrace("正在停止操作......")
self._showTrace("正在停止操作......")
self.__auto_lib_thread.wait(2000)
self.showTrace("操作已停止")
self._showTrace("操作已停止")
self.__auto_lib_thread.AutoLibWorkerIsFinished.disconnect(self.onStopButtonClicked)
self.__auto_lib_thread.AutoLibWorkerFinishedWithError.disconnect(self.onStopButtonClicked)
self.__auto_lib_thread.deleteLater()
@@ -414,6 +397,6 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
msg = self.MessageEdit.text().strip()
if not msg:
return
self.showMsg(msg)
self.__input_queue.put(msg) # put message to input queue
self._showMsg(msg)
self._input_queue.put(msg) # put message to input queue
self.MessageEdit.clear()
+3 -3
View File
@@ -20,7 +20,7 @@ from operators.AutoLib import AutoLib
from utils.ConfigReader import ConfigReader
class AutoLibWorker(QThread, MsgBase):
class AutoLibWorker(MsgBase, QThread):
AutoLibWorkerIsFinished = Signal()
AutoLibWorkerFinishedWithError = Signal()
@@ -32,8 +32,8 @@ class AutoLibWorker(QThread, MsgBase):
config_paths: dict
):
super().__init__(input_queue=input_queue, output_queue=output_queue)
MsgBase.__init__(self, input_queue, output_queue)
QThread.__init__(self)
self.__config_paths = config_paths
+177
View File
@@ -0,0 +1,177 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
You may use, modify, and distribute this file under the terms of the MIT License.
See the LICENSE file for details.
"""
from PySide6.QtCore import (
Qt, Slot, Signal
)
from PySide6.QtWidgets import (
QDialog, QLabel, QHBoxLayout, QVBoxLayout,
QPushButton,
)
from PySide6.QtGui import (
QCloseEvent
)
from gui.ALSeatMapView import ALSeatMapView
class ALSeatMapSelectDialog(QDialog):
seatMapSelectDialogClosed = Signal(list)
def __init__(
self,
parent: QDialog = None,
floor: str = "",
room: str = "",
seats_data: str = ""
):
super().__init__(parent)
self.__floor = floor
self.__room = room
self.__seats_data = seats_data
self.__confirmed = False
self.setupUi()
self.connectSignals()
def setupUi(
self
):
self.setModal(True)
self.setMinimumSize(800, 600)
self.resize(800, 600)
self.setWindowTitle(f"选择楼层座位 - AutoLibrary")
self.SeatMapWidgetMainLayout = QVBoxLayout(self)
self.SeatMapWidgetMainLayout.setContentsMargins(5, 5, 5, 5)
self.SeatMapWidgetMainLayout.setSpacing(5)
self.TitleLabel = QLabel(f"楼层座位分布图: {self.__floor}-{self.__room}")
self.TitleLabel.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.TitleLabel.setStyleSheet("font-size: 16px; font-weight: bold; margin: 10px;")
self.SeatMapWidgetMainLayout.addWidget(self.TitleLabel)
self.SeatMapGraphicsView = ALSeatMapView(None, self.__seats_data)
self.SeatMapWidgetMainLayout.addWidget(self.SeatMapGraphicsView)
self.TipsLabel = QLabel(
" 点击座位进行选择/取消选择, 最多选择1个座位 \n"
" [操作方法: Ctrl+鼠标滚轮缩放 | 滚轮/拖拽/方向键 移动]"
)
self.TipsLabel.setAlignment(Qt.AlignmentFlag.AlignLeft)
self.TipsLabel.setStyleSheet("color: #666; margin: 5px;")
self.SeatMapWidgetMainLayout.addWidget(self.TipsLabel)
self.ConfirmButton = QPushButton("确认")
self.ConfirmButton.setFixedSize(80, 25)
self.ConfirmButton.setAutoDefault(True)
self.ConfirmButton.setDefault(True)
self.CancelButton = QPushButton("取消")
self.CancelButton.setFixedSize(80, 25)
self.SeatMapWidgetControlLayout = QHBoxLayout()
self.SeatMapWidgetControlLayout.setContentsMargins(0, 0, 0, 0)
self.SeatMapWidgetControlLayout.setSpacing(5)
self.SeatMapWidgetControlLayout.setAlignment(Qt.AlignmentFlag.AlignRight)
self.SeatMapWidgetControlLayout.addWidget(self.CancelButton)
self.SeatMapWidgetControlLayout.addWidget(self.ConfirmButton)
self.SeatMapWidgetMainLayout.addLayout(self.SeatMapWidgetControlLayout)
def connectSignals(
self
):
self.ConfirmButton.clicked.connect(self.onConfirmButtonClicked)
self.CancelButton.clicked.connect(self.onCancelButtonClicked)
def showEvent(
self,
event
):
result = super().showEvent(event)
screen_rect = self.screen().geometry()
target_pos = self.parent().geometry().center()
target_pos.setX(target_pos.x() - self.width()//2)
target_pos.setY(target_pos.y() - self.height()//2)
if target_pos.x() < 0:
target_pos.setX(0)
if target_pos.x() + self.width() > screen_rect.width():
target_pos.setX(screen_rect.width() - self.width())
if target_pos.y() < 0:
target_pos.setY(0)
if target_pos.y() + self.height() > screen_rect.height():
target_pos.setY(screen_rect.height() - self.height())
self.move(target_pos)
return result
def closeEvent(
self,
event: QCloseEvent
):
if not self.__confirmed:
self.clearSelections()
self.reject()
else:
self.accept()
self.seatMapSelectDialogClosed.emit(self.getSelectedSeats())
super().closeEvent(event)
def selectSeat(
self,
seat_number: str
):
self.SeatMapGraphicsView.selectSeat(seat_number)
def selectSeats(
self,
seat_numbers: list[str]
) -> bool:
return self.SeatMapGraphicsView.selectSeats(seat_numbers)
def getSelectedSeats(
self
) -> list[str]:
return self.SeatMapGraphicsView.getSelectedSeats()
def clearSelections(
self
):
self.SeatMapGraphicsView.clearSelections()
@Slot()
def onConfirmButtonClicked(
self
):
self.__confirmed = True
self.accept()
@Slot()
def onCancelButtonClicked(
self
):
self.__confirmed = False
self.reject()
+1 -1
View File
@@ -1,4 +1,4 @@
seats_maps = {
ALSeatMapTable = {
"2": {
"1": """
,,,,,,,,,,,039A,039B,,040A,040B,,041A,041B,,042A,042B,,043A,043B,,044A,044B,,,,,,,,,
+187
View File
@@ -0,0 +1,187 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
You may use, modify, and distribute this file under the terms of the MIT License.
See the LICENSE file for details.
"""
from PySide6.QtCore import (
Qt, Slot, QEvent
)
from PySide6.QtWidgets import (
QFrame, QWidget,
QGridLayout, QGraphicsView, QGraphicsScene, QGraphicsItem
)
from PySide6.QtGui import (
QPainter, QWheelEvent
)
from gui.ALSeatFrame import ALSeatFrame
class ALSeatMapView(QGraphicsView):
def __init__(
self,
parent: QWidget = None,
seats_data: dict = {},
):
super().__init__(parent)
self.__seats_data = seats_data
self.__selected_seats = []
self.__seat_frames = {}
self.setupUi()
@staticmethod
def formatSeatNumber(
seat_number: str
) -> str:
if seat_number and not seat_number[-1].isdigit():
digits = seat_number[:-1]
letter = seat_number[-1]
return digits.zfill(3) + letter
return seat_number.zfill(3)
def eventFilter(
self,
watched,
event
):
if (watched is self.viewport() and
event.type() == QEvent.Type.Wheel and
event.modifiers() == Qt.KeyboardModifier.ControlModifier
):
self.zoomGraphicsView(event)
return True
return super().eventFilter(watched, event)
def zoomGraphicsView(
self,
event: QWheelEvent
):
delta = event.angleDelta().y()
min_scale = 0.1
max_scale = 4.0
current_scale = self.transform().m11()
zoom_factor = 1.2 if delta > 0 else 1/1.2
target_scale = current_scale*zoom_factor
if target_scale < min_scale and delta < 0:
return
if target_scale > max_scale and delta > 0:
return
self.setTransformationAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse)
self.scale(zoom_factor, zoom_factor)
def setupUi(
self
):
self.SeatMapGraphicsScene = QGraphicsScene(self)
self.setScene(self.SeatMapGraphicsScene)
self.setRenderHint(QPainter.RenderHint.LosslessImageRendering)
self.setDragMode(QGraphicsView.DragMode.ScrollHandDrag)
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
self.viewport().installEventFilter(self)
self.SeatsContainerWidget = QWidget()
self.SeatsContainerLayout = QGridLayout(self.SeatsContainerWidget)
self.setupSeatMap()
self.ContainerProxy = self.SeatMapGraphicsScene.addWidget(self.SeatsContainerWidget)
self.ContainerProxy.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable, False)
def setupSeatMap(
self
):
rows = self.__seats_data.strip().split("\n")
for row_idx, row in enumerate(rows):
col_idx = 0
seats_number = [seat.strip() for seat in row.split(",")]
for seat_number in seats_number:
if seat_number:
seat_widget = ALSeatFrame(seat_number)
seat_widget.clicked.connect(self.onSeatClicked)
self.SeatsContainerLayout.addWidget(seat_widget, row_idx, col_idx)
self.__seat_frames[seat_number] = seat_widget
else:
spacer = QFrame()
spacer.setFixedSize(20, 30)
spacer.setStyleSheet("background-color: transparent; border: none;")
self.SeatsContainerLayout.addWidget(spacer, row_idx, col_idx)
col_idx += 1
self.SeatsContainerLayout.setSpacing(20)
self.SeatsContainerLayout.setContentsMargins(20, 20, 20, 20)
self.SeatsContainerWidget.adjustSize()
def selectSeat(
self,
seat_number: str
):
if len(self.__selected_seats) >= 1:
return
seat_number = self.formatSeatNumber(seat_number)
if seat_number not in self.__seat_frames:
return
widget = self.__seat_frames[seat_number]
if widget.isSelected():
return
widget.toggleSelection()
self.__selected_seats.append(seat_number)
def selectSeats(
self,
selected_seats: list
):
self.clearSelections()
for seat_number in selected_seats:
self.selectSeat(seat_number)
def getSelectedSeats(
self
) -> list[str]:
return self.__selected_seats
def clearSelections(
self
):
seats_to_clear = self.__selected_seats.copy()
for seat_number in seats_to_clear:
if seat_number not in self.__seat_frames:
continue
widget = self.__seat_frames[seat_number]
if widget.isSelected():
widget.toggleSelection()
self.__selected_seats = []
@Slot(str)
def onSeatClicked(
self,
seat_number: str
):
if seat_number in self.__selected_seats:
self.__selected_seats.remove(seat_number)
else:
if len(self.__selected_seats) < 1:
self.__selected_seats.append(seat_number)
else:
self.__seat_frames[seat_number].toggleSelection()
-286
View File
@@ -1,286 +0,0 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
You may use, modify, and distribute this file under the terms of the MIT License.
See the LICENSE file for details.
"""
from PySide6.QtCore import (
Qt, Slot, Signal, QEvent
)
from PySide6.QtWidgets import (
QFrame, QWidget, QLabel, QHBoxLayout, QVBoxLayout,
QGridLayout, QGraphicsView, QGraphicsScene, QGraphicsItem,
QPushButton,
)
from PySide6.QtGui import (
QPainter, QWheelEvent, QCloseEvent
)
from gui.ALSeatFrame import ALSeatFrame
class ALSeatMapWidget(QWidget):
seatMapWidgetClosed = Signal(list)
def __init__(
self,
parent: QWidget = None,
floor: str = "",
room: str = "",
seats_data: dict = {},
):
super().__init__(parent)
self.__floor = floor
self.__room = room
self.__seats_data = seats_data
self.__selected_seats = []
self.__seat_frames = {}
self.__confirmed = False
self.setupUi()
self.connectSignals()
@staticmethod
def formatSeatNumber(
seat_number: str
) -> str:
if seat_number and not seat_number[-1].isdigit():
digits = seat_number[:-1]
letter = seat_number[-1]
return digits.zfill(3) + letter
return seat_number.zfill(3)
def setupUi(
self
):
self.setWindowFlags(Qt.WindowType.Window)
self.setWindowModality(Qt.WindowModality.ApplicationModal)
self.setMinimumSize(800, 600)
self.resize(800, 600)
self.setWindowTitle(f"选择楼层座位 - AutoLibrary")
self.SeatMapWidgetMainLayout = QVBoxLayout(self)
self.SeatMapWidgetMainLayout.setContentsMargins(5, 5, 5, 5)
self.SeatMapWidgetMainLayout.setSpacing(5)
self.TitleLabel = QLabel(f"楼层座位分布图: {self.__floor}-{self.__room}")
self.TitleLabel.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.TitleLabel.setStyleSheet("font-size: 16px; font-weight: bold; margin: 10px;")
self.SeatMapWidgetMainLayout.addWidget(self.TitleLabel)
self.SeatMapGraphicsView = QGraphicsView(self)
self.SeatMapGraphicsScene = QGraphicsScene(self)
self.SeatMapGraphicsView.setScene(self.SeatMapGraphicsScene)
self.SeatMapGraphicsView.setRenderHint(QPainter.RenderHint.LosslessImageRendering)
self.SeatMapGraphicsView.setDragMode(QGraphicsView.DragMode.ScrollHandDrag)
self.SeatMapGraphicsView.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
self.SeatMapGraphicsView.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
self.SeatMapGraphicsView.viewport().installEventFilter(self)
self.SeatsContainerWidget = QWidget()
self.SeatsContainerLayout = QGridLayout(self.SeatsContainerWidget)
self.createSeatMap()
self.ContainerProxy = self.SeatMapGraphicsScene.addWidget(self.SeatsContainerWidget)
self.ContainerProxy.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable, False)
self.SeatMapWidgetMainLayout.addWidget(self.SeatMapGraphicsView)
self.TipsLabel = QLabel(
" 点击座位进行选择/取消选择, 最多选择1个座位 \n"
" [操作方法: Ctrl+鼠标滚轮缩放 | 滚轮/拖拽/方向键 移动]"
)
self.TipsLabel.setAlignment(Qt.AlignmentFlag.AlignLeft)
self.TipsLabel.setStyleSheet("color: #666; margin: 5px;")
self.SeatMapWidgetMainLayout.addWidget(self.TipsLabel)
self.ConfirmButton = QPushButton("确认")
self.ConfirmButton.setFixedSize(80, 25)
self.ConfirmButton.setAutoDefault(True)
self.ConfirmButton.setDefault(True)
self.CancelButton = QPushButton("取消")
self.CancelButton.setFixedSize(80, 25)
self.SeatMapWidgetControlLayout = QHBoxLayout()
self.SeatMapWidgetControlLayout.setContentsMargins(0, 0, 0, 0)
self.SeatMapWidgetControlLayout.setSpacing(5)
self.SeatMapWidgetControlLayout.setAlignment(Qt.AlignmentFlag.AlignRight)
self.SeatMapWidgetControlLayout.addWidget(self.CancelButton)
self.SeatMapWidgetControlLayout.addWidget(self.ConfirmButton)
self.SeatMapWidgetMainLayout.addLayout(self.SeatMapWidgetControlLayout)
def connectSignals(
self
):
self.ConfirmButton.clicked.connect(self.onConfirmButtonClicked)
self.CancelButton.clicked.connect(self.onCancelButtonClicked)
def showEvent(
self,
event
):
result = super().showEvent(event)
screen_rect = self.screen().geometry()
target_pos = self.parent().geometry().center()
target_pos.setX(target_pos.x() - self.width()//2)
target_pos.setY(target_pos.y() - self.height()//2)
if target_pos.x() < 0:
target_pos.setX(0)
if target_pos.x() + self.width() > screen_rect.width():
target_pos.setX(screen_rect.width() - self.width())
if target_pos.y() < 0:
target_pos.setY(0)
if target_pos.y() + self.height() > screen_rect.height():
target_pos.setY(screen_rect.height() - self.height())
self.move(target_pos)
return result
def closeEvent(
self,
event: QCloseEvent
):
if not self.__confirmed:
self.clearSelections()
self.seatMapWidgetClosed.emit(self.__selected_seats)
super().closeEvent(event)
def eventFilter(
self,
watched,
event
):
if (watched is self.SeatMapGraphicsView.viewport() and
event.type() == QEvent.Type.Wheel and
event.modifiers() == Qt.KeyboardModifier.ControlModifier
):
self.zoomGraphicsView(event)
return True
return super().eventFilter(watched, event)
def zoomGraphicsView(
self,
event: QWheelEvent
):
delta = event.angleDelta().y()
zoom_factor = 1.2 if delta > 0 else 1/1.2
self.SeatMapGraphicsView.setTransformationAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse)
self.SeatMapGraphicsView.scale(zoom_factor, zoom_factor)
def createSeatMap(
self
):
rows = self.__seats_data.strip().split("\n")
for row_idx, row in enumerate(rows):
col_idx = 0
seats_number = [seat.strip() for seat in row.split(",")]
for seat_number in seats_number:
if seat_number:
seat_widget = ALSeatFrame(seat_number)
seat_widget.clicked.connect(self.onSeatClicked)
self.SeatsContainerLayout.addWidget(seat_widget, row_idx, col_idx)
self.__seat_frames[seat_number] = seat_widget
else:
spacer = QFrame()
spacer.setFixedSize(20, 30)
spacer.setStyleSheet("background-color: transparent; border: none;")
self.SeatsContainerLayout.addWidget(spacer, row_idx, col_idx)
col_idx += 1
self.SeatsContainerLayout.setSpacing(20)
self.SeatsContainerLayout.setContentsMargins(20, 20, 20, 20)
self.SeatsContainerWidget.adjustSize()
def selectSeat(
self,
seat_number: str
):
if len(self.__selected_seats) >= 1:
return
seat_number = self.formatSeatNumber(seat_number)
if seat_number not in self.__seat_frames:
return
widget = self.__seat_frames[seat_number]
if widget.isSelected():
return
widget.toggleSelection()
self.__selected_seats.append(seat_number)
def selectSeats(
self,
selected_seats: list
):
self.clearSelections()
for seat_number in selected_seats:
self.selectSeat(seat_number)
def getSelectedSeats(
self
) -> list[str]:
return self.__selected_seats
def clearSelections(
self
):
seats_to_clear = self.__selected_seats.copy()
for seat_number in seats_to_clear:
if seat_number not in self.__seat_frames:
continue
widget = self.__seat_frames[seat_number]
if widget.isSelected():
widget.toggleSelection()
self.__selected_seats = []
@Slot(str)
def onSeatClicked(
self,
seat_number: str
):
if seat_number in self.__selected_seats:
self.__selected_seats.remove(seat_number)
else:
if len(self.__selected_seats) < 1:
self.__selected_seats.append(seat_number)
else:
self.__seat_frames[seat_number].toggleSelection()
@Slot()
def onConfirmButtonClicked(
self
):
self.__confirmed = True
self.close()
@Slot()
def onCancelButtonClicked(
self
):
self.__confirmed = False
self.close()
@@ -20,10 +20,10 @@ from PySide6.QtWidgets import (
QHBoxLayout, QGridLayout, QDateTimeEdit
)
from gui.Ui_ALAddTimerTaskDialog import Ui_ALAddTimerTaskDialog
from gui.resources.ui.Ui_ALTimerTaskAddDialog import Ui_ALTimerTaskAddDialog
class TimerTaskStatus(Enum):
class ALTimerTaskStatus(Enum):
PENDING = "等待中"
READY = "已就绪"
@@ -33,7 +33,7 @@ class TimerTaskStatus(Enum):
OUTDATED = "已过期"
class ALAddTimerTaskWidget(QDialog, Ui_ALAddTimerTaskDialog):
class ALTimerTaskAddDialog(QDialog, Ui_ALTimerTaskAddDialog):
def __init__(
self,
@@ -128,7 +128,7 @@ class ALAddTimerTaskWidget(QDialog, Ui_ALAddTimerTaskDialog):
"execute_time": execute_time,
"silent": silent,
"add_time": added_time,
"status": TimerTaskStatus.PENDING,
"status": ALTimerTaskStatus.PENDING,
"executed": False
}
@@ -24,21 +24,14 @@ from PySide6.QtGui import (
QCloseEvent
)
from gui.Ui_ALTimerTaskWidget import Ui_ALTimerTaskWidget
from gui.ALAddTimerTaskDialog import ALAddTimerTaskWidget, TimerTaskStatus
from gui.resources.ui.Ui_ALTimerTaskManageWidget import Ui_ALTimerTaskManageWidget
from gui.ALTimerTaskAddDialog import ALTimerTaskAddDialog, ALTimerTaskStatus
from utils.ConfigReader import ConfigReader
from utils.ConfigWriter import ConfigWriter
class SortPolicy(Enum):
BY_NAME = "按名称"
BY_ADD_TIME = "按添加时间"
BY_EXECUTE_TIME = "按执行时间"
class TimerTaskItemWidget(QWidget):
class ALTimerTaskItemWidget(QWidget):
def __init__(
self,
@@ -79,22 +72,22 @@ class TimerTaskItemWidget(QWidget):
self.ItemWidgetLayout.addStretch()
match self.__timer_task["status"]:
case TimerTaskStatus.PENDING:
case ALTimerTaskStatus.PENDING:
TaskStatusText = "等待中"
TaskStatusColor = "#FF9800"
case TimerTaskStatus.READY:
case ALTimerTaskStatus.READY:
TaskStatusText = "已就绪"
TaskStatusColor = "#316BFF"
case TimerTaskStatus.RUNNING:
case ALTimerTaskStatus.RUNNING:
TaskStatusText = "执行中"
TaskStatusColor = "#2294FF"
case TimerTaskStatus.EXECUTED:
case ALTimerTaskStatus.EXECUTED:
TaskStatusText = "已执行"
TaskStatusColor = "#4CAF50"
case TimerTaskStatus.ERROR:
case ALTimerTaskStatus.ERROR:
TaskStatusText = "执行失败"
TaskStatusColor = "#DC0000"
case TimerTaskStatus.OUTDATED:
case ALTimerTaskStatus.OUTDATED:
TaskStatusText = "已过期"
TaskStatusColor = "#DC0000"
TaskStatusLabel = QLabel(TaskStatusText)
@@ -128,17 +121,23 @@ class TimerTaskItemWidget(QWidget):
self.DeleteButton = QPushButton("删除")
self.DeleteButton.setFixedSize(80, 25)
self.ItemWidgetLayout.addWidget(self.DeleteButton)
if self.__timer_task["status"] == TimerTaskStatus.READY\
or self.__timer_task["status"] == TimerTaskStatus.RUNNING:
if self.__timer_task["status"] == ALTimerTaskStatus.READY\
or self.__timer_task["status"] == ALTimerTaskStatus.RUNNING:
self.DeleteButton.setEnabled(False)
self.setFixedHeight(55)
class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget):
class SortPolicy(Enum):
BY_NAME = "按名称"
BY_ADD_TIME = "按添加时间"
BY_EXECUTE_TIME = "按执行时间"
timerTasksChanged = Signal()
timerTaskIsReady = Signal(dict)
timerTaskWidgetClosed = Signal()
timerTasksChanged = Signal()
timerTaskManageWidgetClosed = Signal()
def __init__(
self,
@@ -147,10 +146,9 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
):
super().__init__(parent)
self.__timer_tasks = []
self.__check_timer = None
self.__sort_policy = SortPolicy.BY_EXECUTE_TIME
self.__sort_policy = self.SortPolicy.BY_EXECUTE_TIME
self.__sort_order = Qt.SortOrder.AscendingOrder
self.__timer_tasks_config_path = timer_tasks_config_path
@@ -158,7 +156,7 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
self.connectSignals()
self.setupTimer()
if not self.initializeTimerTasks():
return
raise Exception("定时任务配置文件初始化失败 !")
def connectSignals(
@@ -192,11 +190,6 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
return True
timer_tasks = []
if self.saveTimerTasks(self.__timer_tasks_config_path, copy.deepcopy(timer_tasks)):
QMessageBox.information(
self,
"信息 - AutoLibrary",
f"定时任务配置文件初始化完成: \n{self.__timer_tasks_config_path}"
)
self.__timer_tasks = timer_tasks
self.updateTimerTaskList()
return True
@@ -216,16 +209,10 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
for task in timer_tasks["timer_tasks"]:
task["add_time"] = datetime.strptime(task["add_time"], "%Y-%m-%d %H:%M:%S")
task["execute_time"] = datetime.strptime(task["execute_time"], "%Y-%m-%d %H:%M:%S")
task["status"] = TimerTaskStatus(task["status"])
task["status"] = ALTimerTaskStatus(task["status"])
return timer_tasks["timer_tasks"]
raise Exception("定时任务配置文件格式错误")
except Exception as e:
QMessageBox.warning(
self,
"警告 - AutoLibrary",
f"加载定时任务配置发生错误 ! : {e}\n"\
f"文件路径: {timer_tasks_config_path}"
)
except Exception:
return None
@@ -287,7 +274,7 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
):
self.hide()
self.timerTaskWidgetClosed.emit()
self.timerTaskManageWidgetClosed.emit()
event.ignore()
@@ -297,17 +284,17 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
order: Qt.SortOrder = Qt.SortOrder.AscendingOrder
):
if policy == SortPolicy.BY_NAME:
if policy == self.SortPolicy.BY_NAME:
self.__timer_tasks.sort(
key = lambda x: x["name"],
reverse = order is Qt.SortOrder.DescendingOrder
)
elif policy == SortPolicy.BY_ADD_TIME:
elif policy == self.SortPolicy.BY_ADD_TIME:
self.__timer_tasks.sort(
key = lambda x: x["add_time"],
reverse = order is Qt.SortOrder.DescendingOrder
)
elif policy == SortPolicy.BY_EXECUTE_TIME:
elif policy == self.SortPolicy.BY_EXECUTE_TIME:
self.__timer_tasks.sort(
key = lambda x: x["execute_time"],
reverse = order is Qt.SortOrder.DescendingOrder
@@ -324,15 +311,15 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
invalid = 0
total = len(self.__timer_tasks)
for timer_task in self.__timer_tasks:
if timer_task["status"] == TimerTaskStatus.PENDING:
if timer_task["status"] == ALTimerTaskStatus.PENDING:
pending += 1
elif timer_task["status"] == TimerTaskStatus.READY\
or timer_task["status"] == TimerTaskStatus.RUNNING:
elif timer_task["status"] == ALTimerTaskStatus.READY\
or timer_task["status"] == ALTimerTaskStatus.RUNNING:
in_queue += 1
elif timer_task["status"] == TimerTaskStatus.EXECUTED:
elif timer_task["status"] == ALTimerTaskStatus.EXECUTED:
executed += 1
elif timer_task["status"] == TimerTaskStatus.ERROR\
or timer_task["status"] == TimerTaskStatus.OUTDATED:
elif timer_task["status"] == ALTimerTaskStatus.ERROR\
or timer_task["status"] == ALTimerTaskStatus.OUTDATED:
invalid += 1
self.TotalTaskLabel.setText(f"总任务:{total}")
self.PendingTaskLabel.setText(f"待执行:{pending}")
@@ -350,7 +337,7 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
for timer_task in self.__timer_tasks:
item = QListWidgetItem()
item.setData(Qt.UserRole, timer_task)
widget = TimerTaskItemWidget(self, timer_task)
widget = ALTimerTaskItemWidget(self, timer_task)
widget.DeleteButton.clicked.connect(
lambda _, uuid = timer_task["task_uuid"]: self.deleteTask(uuid)
)
@@ -363,7 +350,7 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
self
):
dialog = ALAddTimerTaskWidget(self)
dialog = ALTimerTaskAddDialog(self)
if dialog.exec() == QDialog.DialogCode.Accepted:
timer_task = dialog.getTimerTask()
self.__timer_tasks.append(timer_task)
@@ -398,8 +385,8 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
return
in_queue_tasks = [
x for x in self.__timer_tasks
if x["status"] == TimerTaskStatus.READY
or x["status"] == TimerTaskStatus.RUNNING
if x["status"] == ALTimerTaskStatus.READY
or x["status"] == ALTimerTaskStatus.RUNNING
]
in_queue_count = len(in_queue_tasks)
if in_queue_count > 0:
@@ -422,13 +409,13 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
for timer_task in self.__timer_tasks:
if timer_task["execute_time"] > now:
continue
if timer_task["status"] is not TimerTaskStatus.PENDING:
if timer_task["status"] is not ALTimerTaskStatus.PENDING:
continue
if timer_task["execute_time"] <= now + timedelta(seconds = -5):
timer_task["status"] = TimerTaskStatus.OUTDATED
timer_task["status"] = ALTimerTaskStatus.OUTDATED
need_update = True
else:
timer_task["status"] = TimerTaskStatus.READY
timer_task["status"] = ALTimerTaskStatus.READY
self.timerTaskIsReady.emit(timer_task)
need_update = True
if need_update:
@@ -441,9 +428,9 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
):
mapping = {
0: SortPolicy.BY_NAME,
1: SortPolicy.BY_ADD_TIME,
2: SortPolicy.BY_EXECUTE_TIME
0: self.SortPolicy.BY_NAME,
1: self.SortPolicy.BY_ADD_TIME,
2: self.SortPolicy.BY_EXECUTE_TIME
}
self.__sort_policy = mapping[policy]
self.updateTimerTaskList()
@@ -479,7 +466,7 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
for task in self.__timer_tasks:
if task["task_uuid"] == timer_task["task_uuid"]:
task["status"] = TimerTaskStatus.RUNNING
task["status"] = ALTimerTaskStatus.RUNNING
self.timerTasksChanged.emit()
@@ -491,7 +478,7 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
for task in self.__timer_tasks:
if task["task_uuid"] == timer_task["task_uuid"]:
task["status"] = TimerTaskStatus.EXECUTED
task["status"] = ALTimerTaskStatus.EXECUTED
self.timerTasksChanged.emit()
@Slot(dict)
@@ -502,5 +489,5 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
for task in self.__timer_tasks:
if task["task_uuid"] == timer_task["task_uuid"]:
task["status"] = TimerTaskStatus.ERROR
task["status"] = ALTimerTaskStatus.ERROR
self.timerTasksChanged.emit()
+4 -4
View File
@@ -21,7 +21,7 @@ from PySide6.QtGui import (
)
class TreeItemType(Enum):
class ALUserTreeItemType(Enum):
GROUP = 0
USER = 1
@@ -111,15 +111,15 @@ class ALUserTreeWidget(QTreeWidget):
if source_item is None:
event.ignore()
return
if source_item.type() == TreeItemType.GROUP.value:
if source_item.type() == ALUserTreeItemType.GROUP.value:
if target_item is not None:
event.ignore()
return
elif source_item.type() == TreeItemType.USER.value:
elif source_item.type() == ALUserTreeItemType.USER.value:
if target_item is None:
event.ignore()
return
if target_item.type() != TreeItemType.GROUP.value:
if target_item.type() != ALUserTreeItemType.GROUP.value:
event.ignore()
return
if target_item.checkState(1) == Qt.CheckState.Unchecked:
+3 -3
View File
@@ -5,11 +5,11 @@
workflow process. Do not edit manually.
This file is auto-generated during the workflow process.
Last updated: 2026-01-17 18:18:12 UTC
Last updated: 2026-02-16 07:04:48 UTC
"""
AL_VERSION = "1.0.4"
AL_TAG = "v1.0.4"
AL_VERSION = "1.0.5"
AL_TAG = "v1.0.5"
AL_COMMIT_SHA = "local"
AL_COMMIT_DATE = "null" # time zone : UTC
AL_BUILD_DATE = "null" # time zone : UTC
-1
View File
@@ -1 +0,0 @@
this folder is used to store the batch scripts.
-1
View File
@@ -1 +0,0 @@
this folder is used to store the config files.

Before

Width:  |  Height:  |  Size: 785 KiB

After

Width:  |  Height:  |  Size: 785 KiB

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

@@ -129,21 +129,24 @@
</widget>
</item>
<item>
<widget class="QTextEdit" name="AboutInfoEdit">
<property name="font">
<font>
<family>Courier New</family>
<bold>false</bold>
</font>
<widget class="QTextBrowser" name="AboutInfoBrowser">
<property name="frameShadow">
<enum>QFrame::Shadow::Plain</enum>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarPolicy::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarPolicy::ScrollBarAlwaysOff</enum>
</property>
<property name="lineWrapMode">
<enum>QTextEdit::LineWrapMode::NoWrap</enum>
</property>
<property name="readOnly">
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::TextInteractionFlag::TextBrowserInteraction</set>
<property name="openLinks">
<bool>true</bool>
</property>
</widget>
</item>
@@ -1356,7 +1356,7 @@
</size>
</property>
<property name="toolTip">
<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>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;详情请参阅 &lt;a href=&quot;https://www.autolibrary.top/manuals&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>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ALAddTimerTaskDialog</class>
<widget class="QDialog" name="ALAddTimerTaskDialog">
<class>ALTimerTaskAddDialog</class>
<widget class="QDialog" name="ALTimerTaskAddDialog">
<property name="geometry">
<rect>
<x>0</x>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ALTimerTaskWidget</class>
<widget class="QWidget" name="ALTimerTaskWidget">
<class>ALTimerTaskManageWidget</class>
<widget class="QWidget" name="ALTimerTaskManageWidget">
<property name="geometry">
<rect>
<x>0</x>
@@ -23,7 +23,7 @@
</size>
</property>
<property name="windowTitle">
<string>定时任务 - AutoLibrary</string>
<string>定时任务管理 - AutoLibrary</string>
</property>
<layout class="QVBoxLayout" name="ALTimerTaskWidgetLayout">
<property name="spacing">
+18 -7
View File
@@ -11,6 +11,7 @@ import os
import queue
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
@@ -41,10 +42,11 @@ class AutoLib(MsgBase):
self.__user_config = None
self.__driver = None
if not self.__initBrowserDriver():
raise Exception("浏览器驱动初始化失败")
raise Exception("浏览器驱动初始化失败 !")
else:
if not self.__initDriverUrl():
raise Exception("浏览器驱动URL初始化失败")
self.close()
raise Exception("浏览器驱动URL初始化失败 !")
self.__initLibOperators()
@@ -64,7 +66,8 @@ class AutoLib(MsgBase):
case "firefox":
driver_options = webdriver.FirefoxOptions()
case _:
raise Exception(f"不支持的浏览器驱动类型: {self.__driver_type} !")
self._showTrace(f"不支持的浏览器驱动类型: {self.__driver_type} !")
return False
if not web_driver_config:
self._showTrace("未配置浏览器驱动参数 !")
@@ -107,7 +110,8 @@ class AutoLib(MsgBase):
# init browser driver
self.__driver_path = web_driver_config.get("driver_path")
if not self.__driver_path:
raise Exception(f"未配置浏览器驱动路径 !")
self._showTrace("未配置浏览器驱动路径 !")
return False
self.__driver_path = os.path.abspath(self.__driver_path)
try:
service = None
@@ -122,7 +126,8 @@ class AutoLib(MsgBase):
self._showTrace(f"Firefox 浏览器驱动初始化略慢, 请耐心等待...")
service = FirefoxService(executable_path=self.__driver_path)
self.__driver = webdriver.Firefox(service=service, options=driver_options)
case _:
case _: # actually will not happen, beacuse we have checked it at the initlization
# of 'driver_options'
raise Exception(f"不支持的浏览器驱动类型: {self.__driver_type}")
self.__driver.implicitly_wait(1)
self.__driver.execute_script(
@@ -183,10 +188,16 @@ class AutoLib(MsgBase):
lib_config = self.__run_config.get("library", None)
if not lib_config:
self._showError("未配置图书馆参数 !")
self._showTrace("未配置图书馆参数 !")
return False
url = lib_config.get("host_url") + lib_config.get("login_url")
self.__driver.get(url)
self.__driver.set_page_load_timeout(5)
try:
self.__driver.get(url)
except TimeoutException:
self.__driver.execute_script("window.stop();")
self._showTrace(f"图书馆登录页面加载超时 ! 请检查网络环境是否正常")
return False
if not self.__waitResponseLoad():
return False
return True
+4 -4
View File
@@ -52,13 +52,13 @@ class ConfigReader:
with open(self.__config_path, 'r', encoding='utf-8') as file:
self.__config_data = json.load(file)
except FileNotFoundError as e:
raise Exception(f"Config file not found: {self.__config_path}") from e
raise Exception(f"配置文件不存在: {self.__config_path}") from e
except PermissionError as e:
raise Exception(f"Without enough permission to read config file: {self.__config_path}") from e
raise Exception(f"没有足够的权限读取配置文件: {self.__config_path}") from e
except json.JSONDecodeError as e:
raise Exception(f"JSON decode error in config file: {self.__config_path}") from e
raise Exception(f"JSON 解析错误: {self.__config_path}") from e
except Exception as e:
raise Exception(f"Unknown error occurred while reading config file: {e}") from e
raise Exception(f"读取配置文件时未知错误: {e}") from e
def getConfigs(
+4 -4
View File
@@ -58,13 +58,13 @@ class ConfigWriter:
with open(self.__config_path, "w", encoding="utf-8") as f:
json.dump(self.__config_data, f, indent=4, sort_keys=False)
except PermissionError as e:
raise Exception(f"Without enough permission to write config file: {self.__config_path}") from e
raise Exception(f"没有足够的权限写入配置文件: {self.__config_path}") from e
except IOError as e:
raise Exception(f"IO error occurred while writing config file: {self.__config_path}") from e
raise Exception(f"写入配置文件时发生 IO 错误: {self.__config_path}") from e
except TypeError as e:
raise Exception(f"Config data contains invalid type that can not be JSON serialized: {e}") from e
raise Exception(f"配置数据包含无法 JSON 序列化的类型: {e}") from e
except Exception as e:
raise Exception(f"Unknown error occurred while writing config file: {e}") from e
raise Exception(f"写入配置文件时未知错误: {e}") from e
def setConfigs(
+1
View File
@@ -0,0 +1 @@
This folder is used to store the template config files.
+18
View File
@@ -0,0 +1,18 @@
{
"library": {
"host_url": "http://10.1.20.7",
"login_url": "/login"
},
"login": {
"auto_captcha": true,
"max_attempt": 3
},
"web_driver": {
"driver_type": "edge",
"driver_path": "",
"headless": false
},
"mode": {
"run_mode": 1
}
}
+3
View File
@@ -0,0 +1,3 @@
{
"groups": []
}
+6
View File
@@ -0,0 +1,6 @@
This folder is used to store the template files.
Directory structure:
templates
|─── configs // template config files