.
diff --git a/LINUX_PORTING.md b/LINUX_PORTING.md
index 962111cd..7ea48490 100644
--- a/LINUX_PORTING.md
+++ b/LINUX_PORTING.md
@@ -88,7 +88,7 @@
- pycaw: 音频控制
- pywin32: Windows API 访问
- comtypes: COM 接口
-
+
- **Linux**:
- pulsectl: PulseAudio 音频控制
- 标准 Linux 系统库
diff --git a/README.md b/README.md
index 0ade8695..9970c462 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,7 @@
🚀 **现代化教育工具** | 🎯 **智能权重算法** | 🎨 **优雅交互体验**
+**✔简体中文** | [English](resources/README_EN.md) | [繁體中文](resources/README_ZH_TW.md)
@@ -25,12 +26,19 @@
> [!note]
->
+>
> SecRandom 本体将基于GNU GPLv3协议开源
->
+>
> GNU GPLv3具有Copyleft特性,也就是说,您可以修改SecRandom的源代码,但是**必须将修改版本同样以GNU GPLv3协议开源**
+---------
+> [!note]
+>
+> **SecRandom v2** 将会在 2025/12/14 (GMT +8:00 中国标准时间) 左右 发布!
+>
+> 敬请关注我们的 BiliBili、QQ 频道中的内容,我们会不定期发布开发进展等信息!
## 📖 目录
+
- [🌟 核心亮点](#-核心亮点)
- [📥 下载](#-下载)
- [📸 软件截图](#-软件截图)
@@ -42,129 +50,83 @@
## 🌟 核心亮点
### 🎯 **智能公平抽取系统**
+
- ✅ **动态权重算法**:基于抽取次数、小组、性别等多维度计算,确保真正的公平性
- ✅ **冷启动保护**:防止新成员权重过低,保证每个人都有平等机会
- ✅ **概率可视化**:直观展示每个成员被抽中的概率,让抽取过程透明化
### 🎨 **现代化用户体验**
+
- ✅ **优雅UI设计**:基于 Fluent Design 的现代化界面,支持浅色/深色主题
- ✅ **悬浮窗模式**:可随时进行抽取,不影响其他工作
- ✅ **语音播报**:抽取结果自动语音播报,支持自定义语音引擎
### 🚀 **强大功能集**
+
- ✅ **多种抽取模式**:单人/多人/小组/性别抽取,满足不同场景需求
- ✅ **智能历史记录**:带时间戳的详细记录,支持自动清理
- ✅ **多名单管理**:支持导入/导出名单,轻松管理不同班级/团队
### 💻 **系统兼容性**
-- ✅ **全平台支持**:完美兼容 Windows 7/10/11 系统,Linux 系统支持正在开发中
+
+- ✅ **全平台支持**:完美兼容 Windows 7/10/11 系统
- ✅ **多架构适配**:原生支持 x64、x86 架构
-- ✅ **开机自启**:支持开机自动启动,随时可用(Windows)
+- ✅ **开机自启**:支持开机自动启动,随时可用
## 📥 下载
### 🌐 官方下载页面
+
- 📥 **[官方下载页面](https://secrandom.netlify.app/download)** - 获取最新稳定版本和测试版本
### 📦 下载源选择
#### 官方渠道
+
- **GitHub 官方源** - 官方发布渠道,海外访问较快,推荐使用
- **123云盘源** - 云盘下载,不限速,适合大文件下载
#### 国内加速镜像
+
- **GitHub 镜像源(ghfast.top)** - 国内加速镜像,速度快且稳定
- **GitHub 镜像源(gh-proxy.com)** - 国内加速镜像,适合网络环境特殊的用户
+## 📸 软件截图
-## 📸 软件截图(v1.1.0.1)
📸 软件截图展示 ✨
-
-
-
-
+
+
+
+
## 📖 公平抽取
-> [!note]
->
-> **简介**:
-> 公平抽取是一种随机抽取方式,它确保每个成员被抽取的权重由系统决定,从而避免不公平的结果。
-> 这种方式适用于需要随机且公平的抽取学生回答问题或进行其他需要公平分配的场景。
-> SecRandom的公平抽取的实现基于动态权重系统,通过多个方面来进行权重的计算。
+### 简介
-### **动态权重系统**
-> [!note]
->
-> 动态权重是SecRandom的公平抽取的核心机制。
-> 它通过以下几个方面来计算每个成员的权重:
-> 1. **总抽取次数**:被抽中次数越多权重越低,避免重复抽取
-> 2. **抽取各小组次数**:平衡不同小组的抽取机会
-> 3. **抽取各性别次数**:确保性别平衡
-> 4. **基础权重**:可自定义的初始权重设置
-> 5. **冷启动保护**:防止新成员权重过低,保证公平性
+公平抽取是一种随机抽取方式,它确保每个成员被抽取的权重由系统决定,从而避免不公平的结果。
+这种方式适用于需要随机且公平的抽取学生回答问题或进行其他需要公平分配的场景。
+SecRandom的公平抽取的实现基于动态权重系统,通过多个方面来进行权重的计算。
+
+### 动态权重系统
+
+动态权重是SecRandom的公平抽取的核心机制。
+它通过以下几个方面来计算每个成员的权重:
+
+1. **总抽取次数**:被抽中次数越多权重越低,避免重复抽取
+2. **抽取各小组次数**:平衡不同小组的抽取机会
+3. **抽取各性别次数**:确保性别平衡
+4. **基础权重**:可自定义的初始权重设置
+5. **冷启动保护**:防止新成员权重过低,保证公平性
## 构建与打包
### 触发构建
-在提交信息中包含 `进行打包` 即可触发自动构建流程。
-### Linux 平台支持
-
-SecRandom 正在移植到 Linux 平台。Linux 版本使用 PulseAudio 进行音频控制。
-
-#### Linux 系统要求
-- Ubuntu 20.04 或更高版本(推荐 Ubuntu 22.04)
-- Python 3.8.10
-- PulseAudio 音频系统
-- X11 或 Wayland 显示服务器
-
-#### Linux 安装步骤
-
-1. 安装系统依赖:
-```bash
-sudo apt-get update
-sudo apt-get install -y \
- libpulse-dev \
- pulseaudio \
- libportaudio2 \
- libsndfile1 \
- libasound2-dev \
- portaudio19-dev \
- libxcb-xinerama0 \
- libxcb-cursor0 \
- libxkbcommon-x11-0 \
- libgl1-mesa-glx \
- libegl1 \
- libdbus-1-3 \
- python3.8 \
- python3.8-venv \
- python3-pip
-```
-
-2. 克隆仓库并安装依赖:
-```bash
-git clone https://github.com/SECTL/SecRandom.git
-cd SecRandom
-git checkout linux-port
-python3.8 -m venv venv
-source venv/bin/activate
-pip install -r requirements-linux.txt
-```
-
-3. 运行应用程序:
-```bash
-python main.py
-```
-
-#### Linux 已知限制
-- 开机自启功能需要手动配置(可以使用 systemd 或桌面环境的自启动设置)
-- 某些 Windows 特定功能可能不可用
-- 音频控制依赖 PulseAudio,其他音频系统可能需要额外配置
+在提交信息中包含 `进行打包` 即可触发自动构建流程。
@@ -190,14 +152,16 @@ python main.py
如果您觉得 SecRandom 对您有帮助,欢迎支持我们的开发工作!
-- **支付宝/微信支付**
-
-
+- **支付宝/微信支付**
+
+- **爱发电**
+[为黎泽懿_Aionflux发电](https://afdian.com/a/lzy0983/)
## 📞 联系方式
* 📧 [邮箱](mailto:lzy.12@foxmail.com)
* 👥 [QQ群 833875216](https://qm.qq.com/q/iWcfaPHn7W)
+* 💬 [QQ频道](https://pd.qq.com/s/4x5dafd34?b=9)
* 🎥 [B站主页](https://space.bilibili.com/520571577)
* 🐛 [问题反馈](https://github.com/SECTL/SecRandom/issues)
@@ -212,9 +176,9 @@ python main.py
>[!TIP]
>
> 📊 **贡献值计算公式**:贡献值 = 文档提交新增行数 x5 + 主程序提交新增行数 x5 + 处理issue x5
->
+>
> 📅 **统计时间范围**:2025.08.01 - 2026.01.31 (中国时间 UTC+8)
->
+>
> 🏗️ **统计仓库**:SECTL/SecRandom, SECTL/SecRandom-docs
## ✨ Star历程
diff --git a/Secrandom.spec b/Secrandom.spec
new file mode 100644
index 00000000..81637721
--- /dev/null
+++ b/Secrandom.spec
@@ -0,0 +1,70 @@
+"""PyInstaller spec leveraging shared packaging utilities."""
+
+from PyInstaller.utils.hooks import collect_data_files
+
+from packaging_utils import (
+ ADDITIONAL_HIDDEN_IMPORTS,
+ collect_data_includes,
+ collect_language_modules,
+ collect_view_modules,
+ normalize_hidden_imports,
+)
+
+block_cipher = None
+
+base_datas = [(str(item.source), item.target) for item in collect_data_includes()]
+
+try:
+ qfluentwidgets_datas = collect_data_files("qfluentwidgets")
+except Exception as exc:
+ print(f"Warning: unable to collect qfluentwidgets resources: {exc}")
+ qfluentwidgets_datas = []
+
+all_datas = base_datas + qfluentwidgets_datas
+
+language_hiddenimports = collect_language_modules()
+view_hiddenimports = collect_view_modules()
+
+all_hiddenimports = normalize_hidden_imports(
+ language_hiddenimports + view_hiddenimports + ADDITIONAL_HIDDEN_IMPORTS
+)
+
+a = Analysis(
+ ["main.py"],
+ pathex=[],
+ binaries=[],
+ datas=all_datas,
+ hiddenimports=all_hiddenimports,
+ hookspath=[],
+ hooksconfig={},
+ runtime_hooks=[],
+ excludes=[],
+ win_no_prefer_redirects=False,
+ win_private_assemblies=False,
+ cipher=block_cipher,
+ noarchive=False,
+)
+
+pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
+
+exe = EXE(
+ pyz,
+ a.scripts,
+ a.binaries,
+ a.zipfiles,
+ a.datas,
+ [],
+ name="SecRandom",
+ debug=False,
+ bootloader_ignore_signals=False,
+ strip=False,
+ upx=True,
+ upx_exclude=[],
+ runtime_tmpdir=None,
+ console=False,
+ disable_windowed_traceback=False,
+ argv_emulation=False,
+ target_arch=None,
+ codesign_identity=None,
+ entitlements_file=None,
+)
diff --git a/app/Language/ZH_CN.py.bak b/app/Language/ZH_CN.py.bak
index eecc9a9e..e231d7a0 100644
--- a/app/Language/ZH_CN.py.bak
+++ b/app/Language/ZH_CN.py.bak
@@ -1062,7 +1062,7 @@ ZH_CN = {
"floating_window_enabled_monitor": {
"name": "选择窗口显示的显示器",
"description": "选择点名通知窗口显示的显示器"
- },
+ },
"floating_window_position": {
"name": "窗口位置",
"description": "设置点名通知窗口的位置",
@@ -1157,7 +1157,7 @@ ZH_CN = {
"floating_window_enabled_monitor": {
"name": "选择窗口显示的显示器",
"description": "选择闪抽通知窗口显示的显示器"
- },
+ },
"floating_window_position": {
"name": "窗口位置",
"description": "设置闪抽通知窗口的位置",
@@ -1252,7 +1252,7 @@ ZH_CN = {
"floating_window_enabled_monitor": {
"name": "选择窗口显示的显示器",
"description": "选择即抽通知窗口显示的显示器"
- },
+ },
"floating_window_position": {
"name": "窗口位置",
"description": "设置即抽通知窗口的位置",
@@ -1347,7 +1347,7 @@ ZH_CN = {
"floating_window_enabled_monitor": {
"name": "选择窗口显示的显示器",
"description": "选择自定义抽通知窗口显示的显示器"
- },
+ },
"floating_window_position": {
"name": "窗口位置",
"description": "设置自定义抽通知窗口的位置",
@@ -1442,7 +1442,7 @@ ZH_CN = {
"floating_window_enabled_monitor": {
"name": "选择窗口显示的显示器",
"description": "选择抽奖通知窗口显示的显示器"
- },
+ },
"floating_window_position": {
"name": "窗口位置",
"description": "设置抽奖通知窗口的位置",
@@ -2094,7 +2094,7 @@ ZH_CN = {
"description": "选择Edge TTS语音",
"combo_items": [
"zh-CN-XiaoxiaoNeural",
- "zh-CN-YunxiNeural",
+ "zh-CN-YunxiNeural",
"zh-CN-XiaoyiNeural",
"en-US-JennyNeural",
"en-US-GuyNeural"
diff --git a/app/Language/__init__.py b/app/Language/__init__.py
new file mode 100644
index 00000000..e5ef124b
--- /dev/null
+++ b/app/Language/__init__.py
@@ -0,0 +1 @@
+# Language package
diff --git a/app/Language/modules/__init__.py b/app/Language/modules/__init__.py
new file mode 100644
index 00000000..4742e65c
--- /dev/null
+++ b/app/Language/modules/__init__.py
@@ -0,0 +1 @@
+# Language modules package
diff --git a/app/Language/modules/basic_settings.py b/app/Language/modules/basic_settings.py
index 983a6bb5..ba6541e2 100644
--- a/app/Language/modules/basic_settings.py
+++ b/app/Language/modules/basic_settings.py
@@ -1,62 +1,71 @@
# 基础设置语言配置
basic_settings = {
"ZH_CN": {
- "title": {"name": "基础设置", "description": "软件基础设置"},
- "basic_function": {"name": "基础功能", "description": "软件基础功能"},
- "data_management": {"name": "数据管理", "description": "数据管理"},
- "personalised": {"name": "个性化", "description": "软件个性化设置"},
+ "title": {"name": "基础设置", "description": "配置软件的基本功能和外观"},
+ "basic_function": {"name": "基础功能", "description": "配置软件的核心功能选项"},
+ "data_management": {
+ "name": "数据管理",
+ "description": "管理软件的数据导入和导出",
+ },
+ "personalised": {"name": "个性化", "description": "自定义软件外观和用户体验"},
"autostart": {
"name": "开机自启",
- "description": "设置软件是否在开机时自动启动",
+ "description": "设置软件是否随系统启动自动运行",
"switchbutton_name": {"enable": "开启", "disable": "关闭"},
},
"check_update": {
- "name": "检查更新",
- "description": "设置是否在启动时检查软件更新",
+ "name": "启动时检查更新",
+ "description": "设置软件启动时是否自动检查新版本",
"switchbutton_name": {"enable": "启用", "disable": "禁用"},
},
"show_startup_window": {
- "name": "显示启动窗口",
- "description": "设置是否在启动时显示启动窗口",
+ "name": "显示启动画面",
+ "description": "设置软件启动时是否显示欢迎画面",
"switchbutton_name": {"enable": "显示", "disable": "不显示"},
},
"export_diagnostic_data": {
"name": "导出诊断数据",
- "description": "设置是否在退出时导出诊断数据",
+ "description": "退出软件时导出诊断信息,用于问题排查",
"pushbutton_name": "导出诊断数据",
},
"export_settings": {
"name": "导出设置",
- "description": "设置是否在退出时导出设置",
+ "description": "将当前设置导出为配置文件,用于备份或迁移",
"pushbutton_name": "导出设置",
},
"import_settings": {
"name": "导入设置",
- "description": "设置是否在启动时导入设置",
+ "description": "从配置文件导入设置,覆盖当前配置",
"pushbutton_name": "导入设置",
},
"export_all_data": {
"name": "导出所有数据",
- "description": "设置是否在退出时导出所有数据",
+ "description": "退出软件时导出所有数据和设置",
"pushbutton_name": "导出所有数据",
},
"import_all_data": {
"name": "导入所有数据",
- "description": "设置是否在启动时导入所有数据",
+ "description": "启动软件时从备份文件恢复所有数据",
"pushbutton_name": "导入所有数据",
},
"dpiScale": {
"name": "DPI缩放",
- "description": "设置软件DPI缩放比例(重启生效)",
- "combo_items": ["100%", "125%", "150%", "175%", "200%", "Auto"],
+ "description": "调整软件界面缩放比例(需要重启软件才能生效)",
+ "combo_items": ["100%", "125%", "150%", "175%", "200%", "自动"],
+ },
+ "font": {
+ "name": "字体",
+ "description": "设置软件界面显示字体(需要重启软件才能生效)",
},
- "font": {"name": "字体", "description": "设置软件字体(重启生效)"},
"theme": {
- "name": "主题",
- "description": "设置软件主题",
- "combo_items": ["浅色", "深色", "跟随系统设置"],
+ "name": "主题模式",
+ "description": "选择软件界面的主题样式",
+ "combo_items": ["浅色", "深色", "跟随系统"],
+ },
+ "theme_color": {"name": "主题颜色", "description": "设置软件界面的主题色彩"},
+ "language": {
+ "name": "显示语言",
+ "description": "切换软件界面语言(需要重启软件才能生效)",
},
- "theme_color": {"name": "主题色", "description": "软件主题色"},
- "language": {"name": "语言", "description": "设置软件语言(重启生效)"},
}
}
diff --git a/app/Language/modules/custom_settings.py b/app/Language/modules/custom_settings.py
index 238705c4..c2ac8764 100644
--- a/app/Language/modules/custom_settings.py
+++ b/app/Language/modules/custom_settings.py
@@ -1,10 +1,6 @@
-# “个性设置”页面的语言文件
+# 个性设置页面的语言文件
-custom_settings = {
- "ZH_CN": {
- "title": {"name": "个性设置", "description": "个性设置"}
- }
-}
+custom_settings = {"ZH_CN": {"title": {"name": "个性设置", "description": "个性设置"}}}
# 页面管理语言配置
page_management = {
@@ -15,128 +11,128 @@
"custom": {"name": "自定义抽设置", "description": "自定义抽设置"},
"roll_call_method": {
"name": "点名控制面板位置",
- "description": "配置点名控制面板位置",
- "combo_items": ["左侧", "右侧", "左侧底部", "右侧底部"]
+ "description": "设置点名控制面板的位置",
+ "combo_items": ["左侧", "右侧", "左侧底部", "右侧底部"],
},
"show_name": {
"name": "名称设置按钮",
"description": "开启后软件将显示名称设置按钮",
- "switchbutton_name": {"enable": "显示", "disable": "隐藏"}
+ "switchbutton_name": {"enable": "显示", "disable": "隐藏"},
},
"reset_roll_call": {
"name": "重置点名按钮",
- "description": "开启后软件将显示重置点名按钮",
- "switchbutton_name": {"enable": "显示", "disable": "隐藏"}
+ "description": "开启后将显示重置点名按钮",
+ "switchbutton_name": {"enable": "显示", "disable": "隐藏"},
},
"roll_call_quantity_control": {
- "name": "增加/减少抽取数量控制条",
- "description": "开启后软件将显示增加/减少抽取数量控制条",
- "switchbutton_name": {"enable": "显示", "disable": "隐藏"}
+ "name": "抽取数量控制条",
+ "description": "控制是否显示调整抽取数量的控制条",
+ "switchbutton_name": {"enable": "显示", "disable": "隐藏"},
},
"roll_call_start_button": {
"name": "开始按钮",
- "description": "开启后软件将显示开始按钮",
- "switchbutton_name": {"enable": "显示", "disable": "隐藏"}
+ "description": "控制是否显示开始点名的按钮",
+ "switchbutton_name": {"enable": "显示", "disable": "隐藏"},
},
"roll_call_list": {
"name": "点名名单切换下拉框",
- "description": "开启后软件将显示点名名单切换下拉框",
- "switchbutton_name": {"enable": "显示", "disable": "隐藏"}
+ "description": "控制是否显示点名名单切换下拉框",
+ "switchbutton_name": {"enable": "显示", "disable": "隐藏"},
},
"roll_call_range": {
"name": "点名范围下拉框",
- "description": "开启后软件将显示点名范围",
- "switchbutton_name": {"enable": "显示", "disable": "隐藏"}
+ "description": "控制是否显示点名范围选择下拉框",
+ "switchbutton_name": {"enable": "显示", "disable": "隐藏"},
},
"roll_call_gender": {
"name": "点名性别范围下拉框",
- "description": "开启后软件将显示点名性别范围下拉框",
- "switchbutton_name": {"enable": "显示", "disable": "隐藏"}
+ "description": "控制是否显示点名性别范围选择下拉框",
+ "switchbutton_name": {"enable": "显示", "disable": "隐藏"},
},
"roll_call_quantity_label": {
"name": "数量标签",
- "description": "开启后软件将显示人数/组数数量标签",
- "switchbutton_name": {"enable": "显示", "disable": "隐藏"}
+ "description": "控制是否显示抽取人数/组数的标签",
+ "switchbutton_name": {"enable": "显示", "disable": "隐藏"},
},
"lottery_method": {
"name": "抽奖控制面板位置",
- "description": "配置抽奖控制面板位置",
- "combo_items": ["左侧", "右侧", "左侧底部", "右侧底部"]
+ "description": "设置抽奖控制面板的位置",
+ "combo_items": ["左侧", "右侧", "左侧底部", "右侧底部"],
},
"show_lottery_name": {
"name": "名称设置按钮",
- "description": "开启后软件将显示名称设置按钮",
- "switchbutton_name": {"enable": "显示", "disable": "隐藏"}
+ "description": "控制是否显示奖品名称设置按钮",
+ "switchbutton_name": {"enable": "显示", "disable": "隐藏"},
},
"reset_lottery": {
"name": "重置抽奖按钮",
- "description": "开启后软件将显示重置抽奖按钮",
- "switchbutton_name": {"enable": "显示", "disable": "隐藏"}
+ "description": "控制是否显示重置抽奖按钮",
+ "switchbutton_name": {"enable": "显示", "disable": "隐藏"},
},
"lottery_quantity_control": {
- "name": "增加/减少抽取数量控制条",
- "description": "开启后软件将显示增加/减少抽取数量控制条",
- "switchbutton_name": {"enable": "显示", "disable": "隐藏"}
+ "name": "抽取数量控制条",
+ "description": "控制是否显示调整抽奖数量的控制条",
+ "switchbutton_name": {"enable": "显示", "disable": "隐藏"},
},
"lottery_start_button": {
"name": "开始按钮",
- "description": "开启后软件将显示开始按钮",
- "switchbutton_name": {"enable": "显示", "disable": "隐藏"}
+ "description": "控制是否显示开始抽奖的按钮",
+ "switchbutton_name": {"enable": "显示", "disable": "隐藏"},
},
"lottery_list": {
"name": "抽奖名单切换下拉框",
- "description": "开启后软件将显示抽奖名单切换下拉框",
- "switchbutton_name": {"enable": "显示", "disable": "隐藏"}
+ "description": "控制是否显示抽奖名单切换下拉框",
+ "switchbutton_name": {"enable": "显示", "disable": "隐藏"},
},
"lottery_quantity_label": {
"name": "数量标签",
- "description": "开启后软件将显示奖数标签",
- "switchbutton_name": {"enable": "显示", "disable": "隐藏"}
+ "description": "控制是否显示中奖数量标签",
+ "switchbutton_name": {"enable": "显示", "disable": "隐藏"},
},
"custom_method": {
"name": "自定义抽控制面板位置",
- "description": "配置自定义抽控制面板位置",
- "combo_items": ["左侧", "右侧", "左侧底部", "右侧底部"]
+ "description": "设置自定义抽控制面板的位置",
+ "combo_items": ["左侧", "右侧", "左侧底部", "右侧底部"],
},
"reset_custom": {
"name": "重置自定义抽按钮",
- "description": "开启后软件将显示重置自定义抽按钮",
- "switchbutton_name": {"enable": "显示", "disable": "隐藏"}
+ "description": "控制是否显示重置自定义抽取按钮",
+ "switchbutton_name": {"enable": "显示", "disable": "隐藏"},
},
"custom_quantity_control": {
- "name": "增加/减少抽取数量控制条",
- "description": "开启后软件将显示增加/减少抽取数量控制条",
- "switchbutton_name": {"enable": "显示", "disable": "隐藏"}
+ "name": "抽取数量控制条",
+ "description": "控制是否显示调整自定义抽取数量的控制条",
+ "switchbutton_name": {"enable": "显示", "disable": "隐藏"},
},
"custom_start_button": {
"name": "开始按钮",
- "description": "开启后软件将显示开始按钮",
- "switchbutton_name": {"enable": "显示", "disable": "隐藏"}
+ "description": "控制是否显示开始自定义抽取的按钮",
+ "switchbutton_name": {"enable": "显示", "disable": "隐藏"},
},
"custom_list": {
"name": "自定义抽名单切换下拉框",
- "description": "开启后软件将显示自定义抽名单切换下拉框",
- "switchbutton_name": {"enable": "显示", "disable": "隐藏"}
+ "description": "控制是否显示自定义抽取名单切换下拉框",
+ "switchbutton_name": {"enable": "显示", "disable": "隐藏"},
},
"custom_range_start": {
"name": "自定义抽范围下拉框",
- "description": "开启后软件将显示自定义抽范围",
- "switchbutton_name": {"enable": "显示", "disable": "隐藏"}
+ "description": "控制是否显示自定义抽取范围选择下拉框",
+ "switchbutton_name": {"enable": "显示", "disable": "隐藏"},
},
"custom_range_end": {
"name": "自定义抽性别范围下拉框",
- "description": "开启后软件将显示自定义抽性别范围下拉框",
- "switchbutton_name": {"enable": "显示", "disable": "隐藏"}
+ "description": "控制是否显示自定义抽取性别范围选择下拉框",
+ "switchbutton_name": {"enable": "显示", "disable": "隐藏"},
},
"draw_custom_method": {
- "name": "抽奖控制面板位置",
- "description": "配置抽奖控制面板位置",
- "switchbutton_name": {"enable": "显示", "disable": "隐藏"}
+ "name": "自定义抽控制面板位置",
+ "description": "控制是否显示自定义抽取控制面板",
+ "switchbutton_name": {"enable": "显示", "disable": "隐藏"},
},
"custom_quantity_label": {
"name": "数量标签",
- "description": "开启后软件将显示人数/组数数量标签",
- "switchbutton_name": {"enable": "显示", "disable": "隐藏"}
- }
+ "description": "控制是否显示自定义抽取人数/组数的标签",
+ "switchbutton_name": {"enable": "显示", "disable": "隐藏"},
+ },
}
-}
\ No newline at end of file
+}
diff --git a/app/Language/modules/extraction_settings.py b/app/Language/modules/extraction_settings.py
index a06d0030..bb86e9fb 100644
--- a/app/Language/modules/extraction_settings.py
+++ b/app/Language/modules/extraction_settings.py
@@ -1,8 +1,6 @@
# 抽取设置语言配置
extraction_settings = {
- "ZH_CN": {
- "title": {"name": "抽取设置", "description": "抽取设置"}
- }
+ "ZH_CN": {"title": {"name": "抽取设置", "description": "抽取设置"}}
}
# 点名设置语言配置
@@ -11,22 +9,25 @@
"title": {"name": "点名设置", "description": "点名设置"},
"extraction_function": {
"name": "抽取功能",
- "description": "点名抽取功能相关设置",
+ "description": "设置点名的抽取功能",
+ },
+ "display_settings": {
+ "name": "显示设置",
+ "description": "设置点名结果的显示方式",
},
- "display_settings": {"name": "显示设置", "description": "点名结果显示相关设置"},
"basic_animation_settings": {
"name": "动画设置",
- "description": "点名动画效果相关设置",
+ "description": "设置点名的动画效果",
},
"color_theme_settings": {
"name": "颜色主题设置",
- "description": "点名结果显示的颜色主题相关设置",
+ "description": "设置点名结果的颜色主题",
},
"student_image_settings": {
"name": "学生头像设置",
- "description": "点名结果显示的学生头像相关设置",
+ "description": "设置点名结果中学生头像的显示",
},
- "music_settings": {"name": "音乐设置", "description": "点名音乐相关设置"},
+ "music_settings": {"name": "音乐设置", "description": "设置点名时播放的音乐"},
"draw_mode": {
"name": "抽取模式",
"description": "设置点名抽取的模式",
@@ -34,24 +35,20 @@
},
"clear_record": {
"name": "清除抽取记录方式",
- "description": "设置清除点名抽取记录的方式",
+ "description": "设置何时清除抽取记录",
"combo_items": ["重启后清除", "直到全部抽取完"],
"combo_items_other": ["重启后清除", "直到全部抽取完", "无需清除"],
},
"half_repeat": {
"name": "半重复抽取次数",
- "description": "设置半重复抽取的次数",
- },
- "clear_time": {
- "name": "抽取后定时清除时间",
- "description": "设置抽取后定时清除记录的时间(秒)",
+ "description": "设置名单中每人被抽中多少次后清除抽取记录",
},
"draw_type": {
"name": "抽取方式",
"description": "设置点名抽取的方式",
"combo_items": ["随机抽取", "公平抽取"],
},
- "font_size": {"name": "字体大小", "description": "设置点名结果显示的字体大小"},
+ "font_size": {"name": "字体大小", "description": "设置点名结果的字体大小"},
"display_format": {
"name": "结果显示格式",
"description": "设置点名结果的显示格式",
@@ -69,7 +66,7 @@
},
"animation_interval": {
"name": "动画间隔",
- "description": "设置点名动画的间隔时间(毫秒)",
+ "description": "设置点名动画的间隔时间(毫秒)",
},
"autoplay_count": {
"name": "自动播放次数",
@@ -91,49 +88,7 @@
},
"open_student_image_folder": {
"name": "学生图片文件夹",
- "description": "管理学生图片文件,图片文件名需与学生姓名一致",
- },
- "animation_music": {
- "name": "动画音乐",
- "description": "设置是否播放动画音乐",
- "switchbutton_name": {"enable": "播放", "disable": "关闭"},
- },
- "result_music": {
- "name": "结果音乐",
- "description": "设置是否播放结果音乐",
- "switchbutton_name": {"enable": "播放", "disable": "关闭"},
- },
- "open_animation_music_folder": {
- "name": "打开动画音乐文件夹",
- "description": "管理动画音乐文件并支持随机播放功能",
- },
- "open_result_music_folder": {
- "name": "打开结果音乐文件夹",
- "description": "管理结果音乐文件并支持随机播放功能",
- },
- "animation_music_volume": {
- "name": "动画音乐音量",
- "description": "设置动画音乐的音量",
- },
- "result_music_volume": {
- "name": "结果音乐音量",
- "description": "设置结果音乐的音量",
- },
- "animation_music_fade_in": {
- "name": "动画音乐淡入时间",
- "description": "设置动画音乐淡入的时间",
- },
- "result_music_fade_in": {
- "name": "结果音乐淡入时间",
- "description": "设置结果音乐淡入的时间",
- },
- "animation_music_fade_out": {
- "name": "动画音乐淡出时间",
- "description": "设置动画音乐淡出的时间",
- },
- "result_music_fade_out": {
- "name": "结果音乐淡出时间",
- "description": "设置结果音乐淡出的时间",
+ "description": "管理学生图片文件,图片文件名需与学生姓名一致",
},
}
}
@@ -141,543 +96,296 @@
# 闪抽设置
quick_draw_settings = {
"ZH_CN": {
- "title": {"name": "闪抽设置","description": "闪抽设置"},
+ "title": {"name": "闪抽设置", "description": "闪抽设置"},
"extraction_function": {
"name": "抽取功能",
- "description": "闪抽抽取功能相关设置"
+ "description": "设置闪抽的抽取功能",
},
"display_settings": {
"name": "显示设置",
- "description": "闪抽结果显示相关设置"
+ "description": "设置闪抽结果的显示方式",
},
"basic_animation_settings": {
"name": "动画设置",
- "description": "闪抽动画效果相关设置"
+ "description": "设置闪抽的动画效果",
},
"color_theme_settings": {
"name": "颜色主题设置",
- "description": "闪抽结果显示的颜色主题相关设置"
+ "description": "设置闪抽结果的颜色主题",
},
"student_image_settings": {
"name": "学生头像设置",
- "description": "闪抽结果显示的学生头像相关设置"
- },
- "music_settings": {
- "name": "音乐设置",
- "description": "闪抽音乐相关设置"
+ "description": "设置闪抽结果中学生头像的显示",
},
+ "music_settings": {"name": "音乐设置", "description": "设置闪抽时播放的音乐"},
"draw_mode": {
"name": "抽取模式",
"description": "设置闪抽抽取的模式",
- "combo_items": ["重复抽取","不重复抽取","半重复抽取"]
+ "combo_items": ["重复抽取", "不重复抽取", "半重复抽取"],
},
"clear_record": {
"name": "清除抽取记录方式",
"description": "设置清除闪抽抽取记录的方式",
- "combo_items": ["重启后清除","直到全部抽取完"],
- "combo_items_other": ["重启后清除","直到全部抽取完","无需清除"]
+ "combo_items": ["重启后清除", "直到全部抽取完"],
+ "combo_items_other": ["重启后清除", "直到全部抽取完", "无需清除"],
},
"half_repeat": {
"name": "半重复抽取次数",
- "description": "设置半重复抽取的次数"
- },
- "clear_time": {
- "name": "抽取后定时清除时间",
- "description": "设置抽取后定时清除记录的时间(秒)"
+ "description": "设置名单中每人被抽中多少次后清除抽取记录",
},
"draw_type": {
"name": "抽取方式",
"description": "设置闪抽抽取的方式",
- "combo_items": ["随机抽取","公平抽取"]
- },
- "font_size": {
- "name": "字体大小",
- "description": "设置闪抽结果显示的字体大小"
+ "combo_items": ["随机抽取", "公平抽取"],
},
+ "font_size": {"name": "字体大小", "description": "设置闪抽结果显示的字体大小"},
"display_format": {
"name": "结果显示格式",
"description": "设置闪抽结果的显示格式",
- "combo_items": ["学号+姓名","姓名","学号"]
+ "combo_items": ["学号+姓名", "姓名", "学号"],
},
"show_random": {
"name": "显示随机组员格式",
"description": "设置随机组员的显示格式",
- "combo_items": ["不显示","组名[换行]姓名","组名[短横杠]姓名"]
+ "combo_items": ["不显示", "组名[换行]姓名", "组名[短横杠]姓名"],
},
"animation": {
"name": "动画模式",
"description": "设置闪抽抽取的动画效果",
- "combo_items": ["手动停止动画","自动播放动画","直接显示结果"]
+ "combo_items": ["手动停止动画", "自动播放动画", "直接显示结果"],
},
"animation_interval": {
"name": "动画间隔",
- "description": "设置闪抽动画的间隔时间(毫秒)"
+ "description": "设置闪抽动画的间隔时间(毫秒)",
},
"autoplay_count": {
"name": "自动播放次数",
- "description": "设置闪抽动画自动播放的次数"
+ "description": "设置闪抽动画自动播放的次数",
},
"animation_color_theme": {
"name": "动画颜色主题",
"description": "设置闪抽动画的颜色主题",
- "combo_items": ["关闭","随机颜色","固定颜色"]
+ "combo_items": ["关闭", "随机颜色", "固定颜色"],
},
"result_color_theme": {
"name": "结果颜色主题",
"description": "设置闪抽结果显示的颜色主题",
- "combo_items": ["关闭","随机颜色","固定颜色"]
+ "combo_items": ["关闭", "随机颜色", "固定颜色"],
},
"animation_fixed_color": {
"name": "动画固定颜色",
- "description": "设置闪抽动画的固定颜色"
+ "description": "设置闪抽动画的固定颜色",
},
"result_fixed_color": {
"name": "结果固定颜色",
- "description": "设置闪抽结果显示的固定颜色"
+ "description": "设置闪抽结果显示的固定颜色",
},
"student_image": {
"name": "显示学生图片",
"description": "设置是否显示学生图片",
- "switchbutton_name": {"enable": "显示","disable": "隐藏"}
+ "switchbutton_name": {"enable": "显示", "disable": "隐藏"},
},
"open_student_image_folder": {
"name": "学生图片文件夹",
- "description": "管理学生图片文件,图片文件名需与学生姓名一致"
- },
- "animation_music": {
- "name": "动画音乐",
- "description": "设置是否播放动画音乐",
- "switchbutton_name": {"enable": "播放","disable": "关闭"}
- },
- "result_music": {
- "name": "结果音乐",
- "description": "设置是否播放结果音乐",
- "switchbutton_name": {"enable": "播放","disable": "关闭"}
- },
- "open_animation_music_folder": {
- "name": "打开动画音乐文件夹",
- "description": "管理动画音乐文件并支持随机播放功能"
- },
- "open_result_music_folder": {
- "name": "打开结果音乐文件夹",
- "description": "管理结果音乐文件并支持随机播放功能"
- },
- "animation_music_volume": {
- "name": "动画音乐音量",
- "description": "设置动画音乐的音量"
- },
- "result_music_volume": {
- "name": "结果音乐音量",
- "description": "设置结果音乐的音量"
- },
- "animation_music_fade_in": {
- "name": "动画音乐淡入时间",
- "description": "设置动画音乐淡入的时间"
- },
- "result_music_fade_in": {
- "name": "结果音乐淡入时间",
- "description": "设置结果音乐淡入的时间"
- },
- "animation_music_fade_out": {
- "name": "动画音乐淡出时间",
- "description": "设置动画音乐淡出的时间"
- },
- "result_music_fade_out": {
- "name": "结果音乐淡出时间",
- "description": "设置结果音乐淡出的时间"
- }
+ "description": "管理学生图片文件,图片文件名需与学生姓名一致",
+ },
}
}
# 即抽设置
instant_draw_settings = {
"ZH_CN": {
- "title": {"name": "即抽设置","description": "即时抽取功能的相关设置"
- },
+ "title": {"name": "即抽设置", "description": "即抽功能的相关设置"},
"extraction_function": {
"name": "抽取功能",
- "description": "即抽抽取功能相关设置"
+ "description": "设置即抽的抽取功能",
},
"display_settings": {
"name": "显示设置",
- "description": "即抽结果显示相关设置"
+ "description": "设置即抽结果的显示方式",
},
"basic_animation_settings": {
"name": "动画设置",
- "description": "即抽动画效果相关设置"
+ "description": "设置即抽的动画效果",
},
"color_theme_settings": {
"name": "颜色主题设置",
- "description": "即抽结果显示的颜色主题相关设置"
+ "description": "设置即抽结果的颜色主题",
},
"student_image_settings": {
"name": "学生头像设置",
- "description": "即抽结果显示的学生头像相关设置"
- },
- "music_settings": {
- "name": "音乐设置",
- "description": "即抽音乐相关设置"
+ "description": "设置即抽结果中学生头像的显示",
},
+ "music_settings": {"name": "音乐设置", "description": "设置即抽时播放的音乐"},
"draw_mode": {
"name": "抽取模式",
"description": "设置即抽抽取的模式",
- "combo_items": [
- "重复抽取",
- "不重复抽取",
- "半重复抽取"
- ]
+ "combo_items": ["重复抽取", "不重复抽取", "半重复抽取"],
},
"clear_record": {
"name": "清除抽取记录方式",
"description": "设置清除即抽抽取记录的方式",
- "combo_items": [
- "重启后清除",
- "直到全部抽取完"
- ],
- "combo_items_other": [
- "重启后清除",
- "直到全部抽取完",
- "无需清除"
- ]
+ "combo_items": ["重启后清除", "直到全部抽取完"],
+ "combo_items_other": ["重启后清除", "直到全部抽取完", "无需清除"],
},
"half_repeat": {
"name": "半重复抽取次数",
- "description": "设置半重复抽取的次数"
- },
- "clear_time": {
- "name": "抽取后定时清除时间",
- "description": "设置抽取后定时清除记录的时间(秒)"
+ "description": "设置名单中每人被抽中多少次后清除抽取记录",
},
"draw_type": {
"name": "抽取方式",
"description": "设置即抽抽取的方式",
- "combo_items": [
- "随机抽取",
- "公平抽取"
- ]
- },
- "font_size": {
- "name": "字体大小",
- "description": "设置即抽结果显示的字体大小"
+ "combo_items": ["随机抽取", "公平抽取"],
},
+ "font_size": {"name": "字体大小", "description": "设置即抽结果的字体大小"},
"display_format": {
"name": "结果显示格式",
"description": "设置即抽结果的显示格式",
- "combo_items": [
- "学号+姓名",
- "姓名",
- "学号"
- ]
+ "combo_items": ["学号+姓名", "姓名", "学号"],
},
"show_random": {
"name": "显示随机组员格式",
"description": "设置随机组员的显示格式",
- "combo_items": [
- "不显示",
- "组名[换行]姓名",
- "组名[短横杠]姓名"
- ]
+ "combo_items": ["不显示", "组名[换行]姓名", "组名[短横杠]姓名"],
},
"animation": {
"name": "动画模式",
"description": "设置即抽抽取的动画效果",
- "combo_items": [
- "手动停止动画",
- "自动播放动画",
- "直接显示结果"
- ]
+ "combo_items": ["手动停止动画", "自动播放动画", "直接显示结果"],
},
"animation_interval": {
"name": "动画间隔",
- "description": "设置即抽动画的间隔时间(毫秒)"
+ "description": "设置即抽动画的间隔时间(毫秒)",
},
"autoplay_count": {
"name": "自动播放次数",
- "description": "设置即抽动画自动播放的次数"
+ "description": "设置即抽动画自动播放的次数",
},
"animation_color_theme": {
"name": "动画颜色主题",
"description": "设置即抽动画的颜色主题",
- "combo_items": [
- "关闭",
- "随机颜色",
- "固定颜色"
- ]
+ "combo_items": ["关闭", "随机颜色", "固定颜色"],
},
"result_color_theme": {
"name": "结果颜色主题",
"description": "设置即抽结果显示的颜色主题",
- "combo_items": [
- "关闭",
- "随机颜色",
- "固定颜色"
- ]
+ "combo_items": ["关闭", "随机颜色", "固定颜色"],
},
"animation_fixed_color": {
"name": "动画固定颜色",
- "description": "设置即抽动画的固定颜色"
+ "description": "设置即抽动画的固定颜色",
},
"result_fixed_color": {
"name": "结果固定颜色",
- "description": "设置即抽结果显示的固定颜色"
+ "description": "设置即抽结果显示的固定颜色",
},
"student_image": {
"name": "显示学生图片",
"description": "设置是否显示学生图片",
- "switchbutton_name": {
- "enable": "显示",
- "disable": "隐藏"
- }
+ "switchbutton_name": {"enable": "显示", "disable": "隐藏"},
},
"open_student_image_folder": {
"name": "学生图片文件夹",
- "description": "管理学生图片文件,图片文件名需与学生姓名一致"
- },
- "animation_music": {
- "name": "动画音乐",
- "description": "设置是否播放动画音乐",
- "switchbutton_name": {
- "enable": "播放",
- "disable": "关闭"
- }
- },
- "result_music": {
- "name": "结果音乐",
- "description": "设置是否播放结果音乐",
- "switchbutton_name": {
- "enable": "播放",
- "disable": "关闭"
- }
- },
- "open_animation_music_folder": {
- "name": "打开动画音乐文件夹",
- "description": "管理动画音乐文件并支持随机播放功能"
- },
- "open_result_music_folder": {
- "name": "打开结果音乐文件夹",
- "description": "管理结果音乐文件并支持随机播放功能"
- },
- "animation_music_volume": {
- "name": "动画音乐音量",
- "description": "设置动画音乐的音量"
- },
- "result_music_volume": {
- "name": "结果音乐音量",
- "description": "设置结果音乐的音量"
- },
- "animation_music_fade_in": {
- "name": "动画音乐淡入时间",
- "description": "设置动画音乐淡入的时间"
- },
- "result_music_fade_in": {
- "name": "结果音乐淡入时间",
- "description": "设置结果音乐淡入的时间"
- },
- "animation_music_fade_out": {
- "name": "动画音乐淡出时间",
- "description": "设置动画音乐淡出的时间"
- },
- "result_music_fade_out": {
- "name": "结果音乐淡出时间",
- "description": "设置结果音乐淡出的时间"
- }
+ "description": "管理学生图片文件,图片文件名需与学生姓名一致",
+ },
}
}
# 自定义抽设置
custom_draw_settings = {
- "ZH_CN": {
- "title": {
- "name": "自定义抽设置",
- "description": "自定义抽设置"
- }
- }
+ "ZH_CN": {"title": {"name": "自定义抽设置", "description": "自定义抽设置"}}
}
# 抽奖设置
lottery_settings = {
"ZH_CN": {
- "title": {
- "name": "抽奖设置",
- "description": "抽奖设置"
- },
+ "title": {"name": "抽奖设置", "description": "抽奖设置"},
"extraction_function": {
"name": "抽取功能",
- "description": "抽奖抽取功能相关设置"
+ "description": "设置抽奖的抽取功能",
},
"display_settings": {
"name": "显示设置",
- "description": "抽奖结果显示相关设置"
+ "description": "设置抽奖结果的显示方式",
},
"basic_animation_settings": {
"name": "动画设置",
- "description": "抽奖动画效果相关设置"
+ "description": "设置抽奖的动画效果",
},
"color_theme_settings": {
"name": "颜色主题设置",
- "description": "抽奖结果显示的颜色主题相关设置"
+ "description": "设置抽奖结果的颜色主题",
},
"student_image_settings": {
"name": "奖品图片设置",
- "description": "抽奖结果显示的奖品图片相关设置"
- },
- "music_settings": {
- "name": "音乐设置",
- "description": "抽奖音乐相关设置"
+ "description": "设置抽奖结果中奖品图片的显示",
},
+ "music_settings": {"name": "音乐设置", "description": "设置抽奖时播放的音乐"},
"draw_mode": {
"name": "抽取模式",
"description": "设置抽奖抽取的模式",
- "combo_items": [
- "重复抽取",
- "不重复抽取",
- "半重复抽取"
- ]
+ "combo_items": ["重复抽取", "不重复抽取", "半重复抽取"],
},
"clear_record": {
"name": "清除抽取记录方式",
"description": "设置清除抽奖抽取记录的方式",
- "combo_items": [
- "重启后清除",
- "直到全部抽取完"
- ],
- "combo_items_other": [
- "重启后清除",
- "直到全部抽取完",
- "无需清除"
- ]
+ "combo_items": ["重启后清除", "直到全部抽取完"],
+ "combo_items_other": ["重启后清除", "直到全部抽取完", "无需清除"],
},
"half_repeat": {
"name": "半重复抽取次数",
- "description": "设置半重复抽取的次数"
- },
- "clear_time": {
- "name": "抽取后定时清除时间",
- "description": "设置抽取后定时清除记录的时间(秒)"
+ "description": "设置名单中每人被抽中多少次后清除抽取记录",
},
"draw_type": {
"name": "抽取方式",
"description": "设置抽奖抽取的方式",
- "combo_items": [
- "随机抽取",
- "公平抽取"
- ]
- },
- "font_size": {
- "name": "字体大小",
- "description": "设置抽奖结果显示的字体大小"
+ "combo_items": ["随机抽取", "公平抽取"],
},
+ "font_size": {"name": "字体大小", "description": "设置抽奖结果显示的字体大小"},
"display_format": {
"name": "结果显示格式",
"description": "设置抽奖结果的显示格式",
- "combo_items": [
- "序号+名称",
- "名称",
- "序号"
- ]
+ "combo_items": ["序号+名称", "名称", "序号"],
},
"animation": {
"name": "动画模式",
"description": "设置抽奖抽取的动画效果",
- "combo_items": [
- "手动停止动画",
- "自动播放动画",
- "直接显示结果"
- ]
+ "combo_items": ["手动停止动画", "自动播放动画", "直接显示结果"],
},
"animation_interval": {
"name": "动画间隔",
- "description": "设置抽奖动画的间隔时间(毫秒)"
+ "description": "设置抽奖动画的间隔时间(毫秒)",
},
"autoplay_count": {
"name": "自动播放次数",
- "description": "设置抽奖动画自动播放的次数"
+ "description": "设置抽奖动画自动播放的次数",
},
"animation_color_theme": {
"name": "动画颜色主题",
"description": "设置抽奖动画的颜色主题",
- "combo_items": [
- "关闭",
- "随机颜色",
- "固定颜色"
- ]
+ "combo_items": ["关闭", "随机颜色", "固定颜色"],
},
"result_color_theme": {
"name": "结果颜色主题",
"description": "设置抽奖结果显示的颜色主题",
- "combo_items": [
- "关闭",
- "随机颜色",
- "固定颜色"
- ]
+ "combo_items": ["关闭", "随机颜色", "固定颜色"],
},
"animation_fixed_color": {
"name": "动画固定颜色",
- "description": "设置抽奖动画的固定颜色"
+ "description": "设置抽奖动画的固定颜色",
},
"result_fixed_color": {
"name": "结果固定颜色",
- "description": "设置抽奖结果显示的固定颜色"
+ "description": "设置抽奖结果显示的固定颜色",
},
"lottery_image": {
- "name": "显示学生图片",
- "description": "设置是否显示学生图片",
- "switchbutton_name": {
- "enable": "显示",
- "disable": "隐藏"
- }
+ "name": "显示奖品图片",
+ "description": "设置是否显示奖品图片",
+ "switchbutton_name": {"enable": "显示", "disable": "隐藏"},
},
"open_lottery_image_folder": {
"name": "奖品图片文件夹",
- "description": "管理奖品图片文件,图片文件名需与奖品名称一致"
- },
- "animation_music": {
- "name": "动画音乐",
- "description": "设置是否播放动画音乐",
- "switchbutton_name": {
- "enable": "播放",
- "disable": "关闭"
- }
- },
- "result_music": {
- "name": "结果音乐",
- "description": "设置是否播放结果音乐",
- "switchbutton_name": {
- "enable": "播放",
- "disable": "关闭"
- }
- },
- "open_animation_music_folder": {
- "name": "打开动画音乐文件夹",
- "description": "管理动画音乐文件并支持随机播放功能"
- },
- "open_result_music_folder": {
- "name": "打开结果音乐文件夹",
- "description": "管理结果音乐文件并支持随机播放功能"
- },
- "animation_music_volume": {
- "name": "动画音乐音量",
- "description": "设置动画音乐的音量"
- },
- "result_music_volume": {
- "name": "结果音乐音量",
- "description": "设置结果音乐的音量"
- },
- "animation_music_fade_in": {
- "name": "动画音乐淡入时间",
- "description": "设置动画音乐淡入的时间"
- },
- "result_music_fade_in": {
- "name": "结果音乐淡入时间",
- "description": "设置结果音乐淡入的时间"
- },
- "animation_music_fade_out": {
- "name": "动画音乐淡出时间",
- "description": "设置动画音乐淡出的时间"
- },
- "result_music_fade_out": {
- "name": "结果音乐淡出时间",
- "description": "设置结果音乐淡出的时间"
- }
+ "description": "管理奖品图片文件,图片文件名需与奖品名称一致",
+ },
}
-}
\ No newline at end of file
+}
diff --git a/app/Language/modules/history.py b/app/Language/modules/history.py
index 1f617332..0c63f011 100644
--- a/app/Language/modules/history.py
+++ b/app/Language/modules/history.py
@@ -1,44 +1,48 @@
# 历史记录语言配置
-history = {"ZH_CN": {"title": {"name": "历史记录", "description": "历史记录设置"}}}
+history = {
+ "ZH_CN": {
+ "title": {"name": "历史记录", "description": "查看和管理点名及抽奖的历史记录"}
+ }
+}
# 历史记录管理语言配置
history_management = {
"ZH_CN": {
- "title": {"name": "历史记录管理", "description": "历史记录管理"},
+ "title": {"name": "历史记录管理", "description": "管理点名和抽奖的历史记录"},
"roll_call": {
"name": "点名历史记录",
- "description": "配置点名历史记录相关设置",
+ "description": "查看和管理点名历史记录",
},
"lottery_history": {
"name": "抽奖历史记录",
- "description": "配置抽奖历史记录相关设置",
+ "description": "查看和管理抽奖历史记录",
},
"show_roll_call_history": {
"name": "启用点名历史记录",
- "description": "启用点名历史记录",
+ "description": "控制是否显示点名历史记录功能",
"switchbutton_name": {"enable": "启用", "disable": "禁用"},
},
"select_class_name": {
"name": "选择班级",
- "description": "选择要显示历史记录的班级",
+ "description": "选择要查看历史记录的班级",
},
"clear_roll_call_history": {
"name": "清除点名历史记录",
- "description": "清除点名历史记录",
+ "description": "清除所选班级的点名历史记录",
"pushbutton_name": "清除",
},
"show_lottery_history": {
"name": "启用抽奖历史记录",
- "description": "启用抽奖历史记录",
+ "description": "控制是否显示抽奖历史记录功能",
"switchbutton_name": {"enable": "启用", "disable": "禁用"},
},
"select_pool_name": {
"name": "选择奖池",
- "description": "选择要显示历史记录的奖池",
+ "description": "选择要查看历史记录的奖池",
},
"clear_lottery_history": {
"name": "清除抽奖历史记录",
- "description": "清除抽奖历史记录",
+ "description": "清除所选奖池的抽奖历史记录",
"pushbutton_name": "清除",
},
}
@@ -49,48 +53,52 @@
"ZH_CN": {
"title": {
"name": "点名历史记录表格",
- "description": "用于展示和管理点名历史记录的表格",
+ "description": "以表格形式展示点名历史记录",
},
"select_class_name": {
"name": "选择班级",
- "description": "选择要显示历史记录的班级",
+ "description": "选择要查看历史记录的班级",
},
"select_mode": {
- "name": "选择查看模式",
- "description": "选择要查看的历史记录模式",
- "combo_items": ["全部", "时间"],
+ "name": "查看模式",
+ "description": "选择历史记录的查看方式",
+ "combo_items": ["全部记录", "按时间查看"],
},
"HeaderLabels_all_not_weight": {
- "name": ["学号", "姓名", "性别", "小组", "总次数"],
- "description": "点名历史记录表格的列标题",
+ "name": ["学号", "姓名", "性别", "小组", "点名次数"],
+ "description": "点名历史记录表格的列标题(不包含权重)",
},
"HeaderLabels_all_weight": {
- "name": ["学号", "姓名", "性别", "小组", "总次数", "权重"],
- "description": "点名历史记录表格的列标题",
+ "name": ["学号", "姓名", "性别", "小组", "点名次数", "权重"],
+ "description": "点名历史记录表格的列标题(包含权重)",
},
"HeaderLabels_time_not_weight": {
- "name": ["时间", "学号", "姓名", "性别", "小组"],
- "description": "点名历史记录表格的列标题",
+ "name": ["点名时间", "学号", "姓名", "性别", "小组"],
+ "description": "点名历史记录表格的列标题(按时间查看,不包含权重)",
},
"HeaderLabels_time_weight": {
- "name": ["时间", "学号", "姓名", "性别", "小组", "权重"],
- "description": "点名历史记录表格的列标题",
+ "name": ["点名时间", "学号", "姓名", "性别", "小组", "权重"],
+ "description": "点名历史记录表格的列标题(按时间查看,包含权重)",
},
"HeaderLabels_Individual_not_weight": {
- "name": ["时间", "模式", "人数", "性别", "小组"],
- "description": "点名历史记录表格的列标题",
+ "name": ["点名时间", "点名模式", "点名人数", "性别限制", "小组限制"],
+ "description": "点名历史记录表格的列标题(个人记录,不包含权重)",
},
"HeaderLabels_Individual_weight": {
- "name": ["时间", "模式", "人数", "性别", "小组", "权重"],
- "description": "点名历史记录表格的列标题",
+ "name": [
+ "点名时间",
+ "点名模式",
+ "点名人数",
+ "性别限制",
+ "小组限制",
+ "权重",
+ ],
+ "description": "点名历史记录表格的列标题(个人记录,包含权重)",
},
"select_weight": {
- "name": "是否显示权重",
- "description": "选择是否显示权重在表格中",
- "switchbutton_name": {
- "enable": "显示",
- "disable": "隐藏"
- }
+ "name": "显示权重",
+ "description": "是否在表格中显示权重信息",
+ "switchbutton_name": {"enable": "显示", "disable": "隐藏"},
},
}
}
@@ -100,28 +108,28 @@
"ZH_CN": {
"title": {
"name": "抽奖历史记录表格",
- "description": "用于展示和管理抽奖历史记录的表格",
+ "description": "以表格形式展示抽奖历史记录",
},
"select_pool_name": {
"name": "选择奖池",
- "description": "选择要显示历史记录的奖池",
+ "description": "选择要查看历史记录的奖池",
},
"select_mode": {
- "name": "选择查看模式",
- "description": "选择要查看的历史记录模式",
- "combo_items": ["全部", "时间"],
+ "name": "查看模式",
+ "description": "选择历史记录的查看方式",
+ "combo_items": ["全部记录", "按时间查看"],
},
"HeaderLabels_all_weight": {
- "name": ["序号", "名称", "总次数", "权重"],
- "description": "抽奖历史记录表格的列标题",
+ "name": ["序号", "名称", "中奖次数", "权重"],
+ "description": "抽奖历史记录表格的列标题(全部记录)",
},
"HeaderLabels_time_weight": {
- "name": ["时间", "序号", "名称", "权重"],
- "description": "抽奖历史记录表格的列标题",
+ "name": ["抽奖时间", "序号", "名称", "权重"],
+ "description": "抽奖历史记录表格的列标题(按时间查看)",
},
"HeaderLabels_Individual_weight": {
- "name": ["时间", "模式", "数量", "权重"],
- "description": "抽奖历史记录表格的列标题",
+ "name": ["抽奖时间", "抽奖模式", "抽取数量", "权重设置"],
+ "description": "抽奖历史记录表格的列标题(单次记录)",
},
}
-}
\ No newline at end of file
+}
diff --git a/app/Language/modules/list_management.py b/app/Language/modules/list_management.py
index a4b99e7e..47b993c5 100644
--- a/app/Language/modules/list_management.py
+++ b/app/Language/modules/list_management.py
@@ -1,29 +1,38 @@
# 名单管理语言配置
list_management = {
- "ZH_CN": {"title": {"name": "名单管理", "description": "软件名单管理页面"}}
+ "ZH_CN": {"title": {"name": "名单管理", "description": "管理点名和抽奖的名单"}}
}
# 点名名单语言配置
roll_call_list = {
"ZH_CN": {
- "title": {"name": "点名名单", "description": "点名名单"},
- "set_class_name": {"name": "设置班级名称", "description": "设置班级名称"},
- "select_class_name": {"name": "选择班级", "description": "选择班级"},
- "import_student_name": {"name": "导入学生名单", "description": "导入学生名单"},
- "name_setting": {"name": "设置姓名", "description": "设置姓名"},
- "gender_setting": {"name": "设置性别", "description": "设置性别"},
- "group_setting": {"name": "设置班级", "description": "设置班级"},
- "export_student_name": {"name": "导出学生名单", "description": "导出学生名单"},
+ "title": {"name": "点名名单", "description": "管理点名用的学生名单"},
+ "set_class_name": {"name": "设置班级名称", "description": "设置当前班级的名称"},
+ "select_class_name": {
+ "name": "选择班级",
+ "description": "从已有班级中选择一个",
+ },
+ "import_student_name": {
+ "name": "导入学生名单",
+ "description": "从文件导入学生名单",
+ },
+ "name_setting": {"name": "设置姓名", "description": "设置学生姓名"},
+ "gender_setting": {"name": "设置性别", "description": "设置学生性别"},
+ "group_setting": {"name": "设置小组", "description": "设置学生所属小组"},
+ "export_student_name": {
+ "name": "导出学生名单",
+ "description": "将学生名单导出到文件",
+ },
}
}
# 点名表格语言配置
roll_call_table = {
"ZH_CN": {
- "title": {"name": "点名表格", "description": "用于展示和管理点名名单的表格"},
+ "title": {"name": "点名表格", "description": "展示和管理点名名单的表格"},
"select_class_name": {
"name": "选择班级",
- "description": "选择要显示点名表格的班级",
+ "description": "选择要显示的点名班级",
},
"HeaderLabels": {
"name": ["存在", "学号", "姓名", "性别", "小组"],
@@ -35,23 +44,29 @@
# 抽奖名单语言配置
lottery_list = {
"ZH_CN": {
- "title": {"name": "抽奖名单", "description": "抽奖名单"},
- "set_pool_name": {"name": "设置奖池名称", "description": "设置奖池名称"},
- "select_pool_name": {"name": "选择奖池 ", "description": "选择奖池"},
- "import_prize_name": {"name": "导入奖品名单", "description": "导入奖品名单"},
- "prize_setting": {"name": "设置奖品", "description": "设置奖品"},
- "prize_weight_setting": {"name": "设置权重", "description": "设置权重"},
- "export_prize_name": {"name": "导出奖品名单", "description": "导出奖品名单"},
+ "title": {"name": "抽奖名单", "description": "管理抽奖用的奖品名单"},
+ "set_pool_name": {"name": "设置奖池名称", "description": "设置当前奖池的名称"},
+ "select_pool_name": {"name": "选择奖池", "description": "从已有奖池中选择一个"},
+ "import_prize_name": {
+ "name": "导入奖品名单",
+ "description": "从文件导入奖品名单",
+ },
+ "prize_setting": {"name": "设置奖品", "description": "设置奖品名称"},
+ "prize_weight_setting": {"name": "设置权重", "description": "设置奖品中奖权重"},
+ "export_prize_name": {
+ "name": "导出奖品名单",
+ "description": "将奖品名单导出到文件",
+ },
}
}
# 抽奖表格语言配置
lottery_table = {
"ZH_CN": {
- "title": {"name": "抽奖表格", "description": "用于展示和管理抽奖名单的表格"},
+ "title": {"name": "抽奖表格", "description": "展示和管理抽奖名单的表格"},
"select_pool_name": {
"name": "选择奖池",
- "description": "选择要显示抽奖表格的奖池",
+ "description": "选择要显示的抽奖奖池",
},
"HeaderLabels": {
"name": ["存在", "序号", "奖品", "权重"],
diff --git a/app/Language/modules/more_settings.py b/app/Language/modules/more_settings.py
index 8424a9e8..4975c54b 100644
--- a/app/Language/modules/more_settings.py
+++ b/app/Language/modules/more_settings.py
@@ -7,22 +7,43 @@
# 进阶设置语言配置
advanced_settings = {
- "ZH_CN": {
- "title": {"name": "进阶设置", "description": "进阶设置"},
- "advanced_fair_draw": {"name": "公平抽取", "description": "公平抽取算法设置页面"},
- "fair_draw": {"name": "按总抽取次数公平抽取", "description": "是否启用根据总抽取次数进行公平抽取",
- "switchbutton_name": {"enable": "", "disable": ""}},
- "fair_draw_group": {"name": "按组公平抽取", "description": "是否启用按组参与公平抽取计算",
- "switchbutton_name": {"enable": "", "disable": ""}},
- "fair_draw_gender": {"name": "按性别公平抽取", "description": "是否启用按性别参与公平抽取计算",
- "switchbutton_name": {"enable": "","disable": ""}},
- "fair_draw_time": {"name": "按时间公平抽取", "description": "是否启用按时间参与公平抽取计算",
- "switchbutton_name": {"enable": "","disable": ""}},
- "base_weight": {"name": "基础权重", "description": "设置每个选项的基础权重"},
- "min_weight": {"name": "权重范围最小值", "description": "设置每个选项的权重范围最小值"},
- "max_weight": {"name": "权重范围最大值", "description": "设置每个选项的权重范围最大值"},
- }
+ "ZH_CN": {
+ "title": {"name": "进阶设置", "description": "进阶设置"},
+ "advanced_fair_draw": {
+ "name": "公平抽取",
+ "description": "配置公平抽取算法的相关设置",
+ },
+ "fair_draw": {
+ "name": "按总抽取次数公平抽取",
+ "description": "启用后将根据总抽取次数进行公平抽取",
+ "switchbutton_name": {"enable": "", "disable": ""},
+ },
+ "fair_draw_group": {
+ "name": "按组公平抽取",
+ "description": "启用后将按组参与公平抽取计算",
+ "switchbutton_name": {"enable": "", "disable": ""},
+ },
+ "fair_draw_gender": {
+ "name": "按性别公平抽取",
+ "description": "启用后将按性别参与公平抽取计算",
+ "switchbutton_name": {"enable": "", "disable": ""},
+ },
+ "fair_draw_time": {
+ "name": "按时间公平抽取",
+ "description": "启用后将按时间参与公平抽取计算",
+ "switchbutton_name": {"enable": "", "disable": ""},
+ },
+ "base_weight": {"name": "基础权重", "description": "设置每个选项的基础权重值"},
+ "min_weight": {
+ "name": "权重范围最小值",
+ "description": "设置每个选项权重的最小值",
+ },
+ "max_weight": {
+ "name": "权重范围最大值",
+ "description": "设置每个选项权重的最大值",
+ },
}
+}
# 调试设置语言配置
debug = {"ZH_CN": {"title": {"name": "调试设置", "description": "调试设置"}}}
@@ -39,7 +60,7 @@
"contributor": {
"name": "贡献人员",
"description": "点击查看详细贡献者信息",
- "contributor_role_1": "设计 & 创意 & 策划 &\n维护 & 文档& 测试",
+ "contributor_role_1": "设计 & 创意 & 策划 &\n维护 & 文档 & 测试",
"contributor_role_2": "创意 & 维护",
"contributor_role_3": "应用测试 & 文档 & 安装包制作",
"contributor_role_4": "响应式前端页面\n设计及维护 & 文档",
diff --git a/app/Language/modules/notification_settings.py b/app/Language/modules/notification_settings.py
index 27221c2b..06fecea9 100644
--- a/app/Language/modules/notification_settings.py
+++ b/app/Language/modules/notification_settings.py
@@ -7,37 +7,37 @@
roll_call_notification_settings = {
"ZH_CN": {
"title": {"name": "点名通知设置", "description": "点名通知设置"},
- "basic_settings": {"name": "基础设置", "description": "基础设置"},
+ "basic_settings": {"name": "基础设置", "description": "配置通知显示的基础参数"},
"window_mode": {
"name": "窗口模式",
- "description": "设置点名通知窗口的显示模式",
+ "description": "配置点名通知窗口的显示方式",
},
"floating_window_mode": {
"name": "浮动窗口模式",
- "description": "设置点名通知窗口的浮动模式",
+ "description": "配置点名通知浮动窗口的行为",
},
- "call_notification_service": {
+ "call_notification_service": { # TODO: 待完善
"name": "调用通知服务",
- "description": "是否调用通知服务发送结果通知",
+ "description": "启用后将调用系统通知服务发送点名结果",
"switchbutton_name": {"enable": "启用", "disable": "禁用"},
},
"notification_mode": {
"name": "通知模式",
- "description": "设置点名通知的模式",
- "combo_items": ["窗口", "浮窗"],
+ "description": "设置通知显示模式,可选择普通窗口或浮动窗口",
+ "combo_items": ["窗口", "浮动窗口"],
},
"animation": {
- "name": "动画",
- "description": "设置点名通知窗口的动画",
+ "name": "动画效果",
+ "description": "控制点名通知窗口是否显示动画效果",
"switchbutton_name": {"enable": "启用", "disable": "禁用"},
},
"enabled_monitor": {
- "name": "选择窗口显示的显示器",
- "description": "选择点名通知窗口显示的显示器",
+ "name": "显示器选择",
+ "description": "选择用于显示点名通知的显示器",
},
"window_position": {
"name": "窗口位置",
- "description": "设置点名通知窗口的位置",
+ "description": "设置点名通知窗口在屏幕上的显示位置",
"combo_items": [
"中心",
"顶部",
@@ -52,23 +52,23 @@
},
"horizontal_offset": {
"name": "水平偏移",
- "description": "设置点名通知窗口的水平偏移量(像素)",
+ "description": "调整点名通知窗口相对默认位置的水平偏移量(像素)",
},
"vertical_offset": {
"name": "垂直偏移",
- "description": "设置点名通知窗口的垂直偏移量(像素)",
+ "description": "调整点名通知窗口相对默认位置的垂直偏移量(像素)",
},
"transparency": {
"name": "透明度",
- "description": "设置点名通知窗口的透明度(0-100)",
+ "description": "调整点名通知窗口的透明度,数值越小越透明(0-100)",
},
"floating_window_enabled_monitor": {
- "name": "选择窗口显示的显示器",
- "description": "选择点名通知窗口显示的显示器",
+ "name": "显示器选择",
+ "description": "选择用于显示点名通知浮动窗口的显示器",
},
"floating_window_position": {
- "name": "窗口位置",
- "description": "设置点名通知窗口的位置",
+ "name": "浮动窗口位置",
+ "description": "设置点名通知浮动窗口在屏幕上的显示位置",
"combo_items": [
"顶部",
"底部",
@@ -82,15 +82,15 @@
},
"floating_window_horizontal_offset": {
"name": "水平偏移",
- "description": "设置点名通知窗口的水平偏移量(像素)",
+ "description": "调整点名通知浮动窗口相对默认位置的水平偏移量(像素)",
},
"floating_window_vertical_offset": {
"name": "垂直偏移",
- "description": "设置点名通知窗口的垂直偏移量(像素)",
+ "description": "调整点名通知浮动窗口相对默认位置的垂直偏移量(像素)",
},
"floating_window_transparency": {
- "name": "浮动窗口透明度",
- "description": "设置点名通知窗口的透明度",
+ "name": "透明度",
+ "description": "调整点名通知浮动窗口的透明度,数值越小越透明(0-100)",
},
}
}
@@ -100,43 +100,37 @@
"ZH_CN": {
"title": {
"name": "闪抽通知设置",
- "description": "闪抽通知设置"
+ "description": "配置闪抽结果通知的显示方式和参数",
},
"basic_settings": {
"name": "基础设置",
- "description": "基础设置"
+ "description": "配置闪抽通知的基础显示参数",
},
"window_mode": {
"name": "窗口模式",
- "description": "设置闪抽通知窗口的显示模式"
+ "description": "设置闪抽通知窗口的显示方式",
},
"floating_window_mode": {
"name": "浮动窗口模式",
- "description": "设置闪抽通知窗口的浮动模式"
+ "description": "设置闪抽通知浮动窗口的行为模式",
},
"notification_mode": {
"name": "通知模式",
- "description": "设置闪抽通知的模式",
- "combo_items": [
- "窗口",
- "浮窗"
- ]
+ "description": "设置闪抽通知的显示模式,可选择普通窗口或浮动窗口",
+ "combo_items": ["窗口", "浮动窗口"],
},
"animation": {
"name": "动画",
- "description": "设置闪抽通知窗口的动画",
- "switchbutton_name": {
- "enable": "启用",
- "disable": "禁用"
- }
+ "description": "设置闪抽通知窗口的显示动画效果",
+ "switchbutton_name": {"enable": "启用", "disable": "禁用"},
},
"enabled_monitor": {
- "name": "选择窗口显示的显示器",
- "description": "选择闪抽通知窗口显示的显示器"
+ "name": "选择闪抽通知显示的显示器",
+ "description": "选择闪抽通知窗口显示的显示器",
},
"window_position": {
"name": "窗口位置",
- "description": "设置闪抽通知窗口的位置",
+ "description": "设置闪抽通知窗口在屏幕上的显示位置",
"combo_items": [
"中心",
"顶部",
@@ -146,28 +140,28 @@
"顶部左侧",
"顶部右侧",
"底部左侧",
- "底部右侧"
- ]
+ "底部右侧",
+ ],
},
"horizontal_offset": {
"name": "水平偏移",
- "description": "设置闪抽通知窗口的水平偏移量(像素)"
+ "description": "设置闪抽通知窗口相对默认位置的水平偏移量(像素)",
},
"vertical_offset": {
"name": "垂直偏移",
- "description": "设置闪抽通知窗口的垂直偏移量(像素)"
+ "description": "设置闪抽通知窗口相对默认位置的垂直偏移量(像素)",
},
"transparency": {
"name": "透明度",
- "description": "设置闪抽通知窗口的透明度(0-100)"
+ "description": "设置闪抽通知窗口的透明度,数值越小越透明(0-100)",
},
"floating_window_enabled_monitor": {
- "name": "选择窗口显示的显示器",
- "description": "选择闪抽通知窗口显示的显示器"
- },
+ "name": "选择闪抽通知显示的显示器",
+ "description": "选择闪抽通知浮动窗口显示的显示器",
+ },
"floating_window_position": {
- "name": "窗口位置",
- "description": "设置闪抽通知窗口的位置",
+ "name": "浮动窗口位置",
+ "description": "设置闪抽通知浮动窗口在屏幕上的显示位置",
"combo_items": [
"顶部",
"底部",
@@ -176,21 +170,21 @@
"顶部左侧",
"顶部右侧",
"底部左侧",
- "底部右侧"
- ]
+ "底部右侧",
+ ],
},
"floating_window_horizontal_offset": {
"name": "水平偏移",
- "description": "设置闪抽通知窗口的水平偏移量(像素)"
+ "description": "设置闪抽通知浮动窗口相对默认位置的水平偏移量(像素)",
},
"floating_window_vertical_offset": {
"name": "垂直偏移",
- "description": "设置闪抽通知窗口的垂直偏移量(像素)"
+ "description": "设置闪抽通知浮动窗口相对默认位置的垂直偏移量(像素)",
},
"floating_window_transparency": {
"name": "浮动窗口透明度",
- "description": "设置闪抽通知窗口的透明度"
- }
+ "description": "设置闪抽通知浮动窗口的透明度,数值越小越透明(0-100)",
+ },
}
}
@@ -199,43 +193,37 @@
"ZH_CN": {
"title": {
"name": "即抽通知设置",
- "description": "即抽通知设置"
+ "description": "配置即抽结果通知的显示方式和参数",
},
"basic_settings": {
"name": "基础设置",
- "description": "基础设置"
+ "description": "配置即抽通知的基础显示参数",
},
"window_mode": {
"name": "窗口模式",
- "description": "设置即抽通知窗口的显示模式"
+ "description": "设置即抽通知窗口的显示方式",
},
"floating_window_mode": {
"name": "浮动窗口模式",
- "description": "设置即抽通知窗口的浮动模式"
+ "description": "设置即抽通知浮动窗口的行为模式",
},
"notification_mode": {
"name": "通知模式",
- "description": "设置即抽通知的模式",
- "combo_items": [
- "窗口",
- "浮窗"
- ]
+ "description": "设置通知显示模式,可选择普通窗口或浮动窗口",
+ "combo_items": ["窗口", "浮动窗口"],
},
"animation": {
"name": "动画",
- "description": "设置即抽通知窗口的动画",
- "switchbutton_name": {
- "enable": "启用",
- "disable": "禁用"
- }
+ "description": "设置即抽通知窗口的显示动画效果",
+ "switchbutton_name": {"enable": "启用", "disable": "禁用"},
},
"enabled_monitor": {
- "name": "选择窗口显示的显示器",
- "description": "选择即抽通知窗口显示的显示器"
+ "name": "选择即抽通知显示的显示器",
+ "description": "选择即抽通知窗口显示的显示器",
},
"window_position": {
"name": "窗口位置",
- "description": "设置即抽通知窗口的位置",
+ "description": "设置即抽通知窗口在屏幕上的显示位置",
"combo_items": [
"中心",
"顶部",
@@ -245,28 +233,28 @@
"顶部左侧",
"顶部右侧",
"底部左侧",
- "底部右侧"
- ]
+ "底部右侧",
+ ],
},
"horizontal_offset": {
"name": "水平偏移",
- "description": "设置即抽通知窗口的水平偏移量(像素)"
+ "description": "设置即抽通知窗口相对默认位置的水平偏移量(像素)",
},
"vertical_offset": {
"name": "垂直偏移",
- "description": "设置即抽通知窗口的垂直偏移量(像素)"
+ "description": "设置即抽通知窗口相对默认位置的垂直偏移量(像素)",
},
"transparency": {
"name": "透明度",
- "description": "设置即抽通知窗口的透明度(0-100)"
+ "description": "设置即抽通知窗口的透明度,数值越小越透明(0-100)",
},
"floating_window_enabled_monitor": {
- "name": "选择窗口显示的显示器",
- "description": "选择即抽通知窗口显示的显示器"
- },
+ "name": "选择即抽通知显示的显示器",
+ "description": "选择即抽通知浮动窗口显示的显示器",
+ },
"floating_window_position": {
- "name": "窗口位置",
- "description": "设置即抽通知窗口的位置",
+ "name": "浮动窗口位置",
+ "description": "设置即抽通知浮动窗口在屏幕上的显示位置",
"combo_items": [
"顶部",
"底部",
@@ -275,71 +263,59 @@
"顶部左侧",
"顶部右侧",
"底部左侧",
- "底部右侧"
- ]
+ "底部右侧",
+ ],
},
"floating_window_horizontal_offset": {
"name": "水平偏移",
- "description": "设置即抽通知窗口的水平偏移量(像素)"
+ "description": "设置即抽通知浮动窗口相对默认位置的水平偏移量(像素)",
},
"floating_window_vertical_offset": {
"name": "垂直偏移",
- "description": "设置即抽通知窗口的垂直偏移量(像素)"
+ "description": "设置即抽通知浮动窗口相对默认位置的垂直偏移量(像素)",
},
"floating_window_transparency": {
"name": "浮动窗口透明度",
- "description": "设置即抽通知窗口的透明度"
- }
+ "description": "设置即抽通知浮动窗口的透明度,数值越小越透明(0-100)",
+ },
}
}
# 自定义抽通知设置
custom_draw_notification_settings = {
"ZH_CN": {
- "title": {
- "name": "自定义抽通知设置",
- "description": "自定义抽通知设置"
- },
- "basic_settings": {
- "name": "基础设置",
- "description": "基础设置"
- },
+ "title": {"name": "自定义抽通知设置", "description": "自定义抽通知设置"},
+ "basic_settings": {"name": "基础设置", "description": "基础设置"},
"window_mode": {
"name": "窗口模式",
- "description": "设置自定义抽通知窗口的显示模式"
+ "description": "设置自定义抽取通知窗口的显示方式",
},
"floating_window_mode": {
"name": "浮动窗口模式",
- "description": "设置自定义抽通知窗口的浮动模式"
+ "description": "设置自定义抽取通知浮动窗口的行为模式",
},
- "call_notification_service": {
+ "call_notification_service": { # TODO: 待完善
"name": "调用通知服务",
- "description": "是否调用通知服务发送结果通知",
+ "description": "是否调用系统通知服务发送自定义抽取结果通知",
"switchbutton_name": {"enable": "启用", "disable": "禁用"},
},
"notification_mode": {
"name": "通知模式",
- "description": "设置自定义抽通知的模式",
- "combo_items": [
- "窗口",
- "浮窗"
- ]
+ "description": "设置通知显示模式,可选择普通窗口或浮动窗口",
+ "combo_items": ["窗口", "浮动窗口"],
},
"animation": {
"name": "动画",
- "description": "设置自定义抽通知窗口的动画",
- "switchbutton_name": {
- "enable": "启用",
- "disable": "禁用"
- }
+ "description": "设置自定义抽取通知窗口的显示动画效果",
+ "switchbutton_name": {"enable": "启用", "disable": "禁用"},
},
"enabled_monitor": {
- "name": "选择窗口显示的显示器",
- "description": "选择自定义抽通知窗口显示的显示器"
+ "name": "选择自定义抽取通知显示的显示器",
+ "description": "选择自定义抽取通知窗口显示的显示器",
},
"window_position": {
"name": "窗口位置",
- "description": "设置自定义抽通知窗口的位置",
+ "description": "设置自定义抽取通知窗口在屏幕上的显示位置",
"combo_items": [
"中心",
"顶部",
@@ -349,28 +325,28 @@
"顶部左侧",
"顶部右侧",
"底部左侧",
- "底部右侧"
- ]
+ "底部右侧",
+ ],
},
"horizontal_offset": {
"name": "水平偏移",
- "description": "设置自定义抽通知窗口的水平偏移量(像素)"
+ "description": "设置自定义抽取通知窗口相对默认位置的水平偏移量(像素)",
},
"vertical_offset": {
"name": "垂直偏移",
- "description": "设置自定义抽通知窗口的垂直偏移量(像素)"
+ "description": "设置自定义抽取通知窗口相对默认位置的垂直偏移量(像素)",
},
"transparency": {
"name": "透明度",
- "description": "设置自定义抽通知窗口的透明度(0-100)"
+ "description": "设置自定义抽取通知窗口的透明度,数值越小越透明(0-100)",
},
"floating_window_enabled_monitor": {
- "name": "选择窗口显示的显示器",
- "description": "选择自定义抽通知窗口显示的显示器"
- },
+ "name": "选择自定义抽取通知显示的显示器",
+ "description": "选择自定义抽取通知浮动窗口显示的显示器",
+ },
"floating_window_position": {
- "name": "窗口位置",
- "description": "设置自定义抽通知窗口的位置",
+ "name": "浮动窗口位置",
+ "description": "设置自定义抽取通知浮动窗口在屏幕上的显示位置",
"combo_items": [
"顶部",
"底部",
@@ -379,21 +355,21 @@
"顶部左侧",
"顶部右侧",
"底部左侧",
- "底部右侧"
- ]
+ "底部右侧",
+ ],
},
"floating_window_horizontal_offset": {
"name": "水平偏移",
- "description": "设置自定义抽通知窗口的水平偏移量(像素)"
+ "description": "设置自定义抽取通知浮动窗口相对默认位置的水平偏移量(像素)",
},
"floating_window_vertical_offset": {
"name": "垂直偏移",
- "description": "设置自定义抽通知窗口的垂直偏移量(像素)"
+ "description": "设置自定义抽取通知浮动窗口相对默认位置的垂直偏移量(像素)",
},
"floating_window_transparency": {
"name": "浮动窗口透明度",
- "description": "设置自定义抽通知窗口的透明度"
- }
+ "description": "设置自定义抽取通知浮动窗口的透明度,数值越小越透明(0-100)",
+ },
}
}
@@ -402,48 +378,42 @@
"ZH_CN": {
"title": {
"name": "抽奖通知设置",
- "description": "抽奖通知设置"
+ "description": "配置抽奖结果通知的显示方式和参数",
},
"basic_settings": {
"name": "基础设置",
- "description": "基础设置"
+ "description": "配置抽奖通知的基础显示参数",
},
"window_mode": {
"name": "窗口模式",
- "description": "设置抽奖通知窗口的显示模式"
+ "description": "设置抽奖通知窗口的显示方式",
},
"floating_window_mode": {
"name": "浮动窗口模式",
- "description": "设置抽奖通知窗口的浮动模式"
+ "description": "设置抽奖通知浮动窗口的行为模式",
},
- "call_notification_service": {
+ "call_notification_service": { # TODO: 对应功能还未实现,暂时不做优化
"name": "调用通知服务",
- "description": "是否调用通知服务发送结果通知",
+ "description": "是否调用系统通知服务发送抽奖结果通知",
"switchbutton_name": {"enable": "启用", "disable": "禁用"},
},
"notification_mode": {
"name": "通知模式",
- "description": "设置抽奖通知的模式",
- "combo_items": [
- "窗口",
- "浮窗"
- ]
+ "description": "设置通知显示模式,可选择普通窗口或浮动窗口",
+ "combo_items": ["窗口", "浮动窗口"],
},
"animation": {
"name": "动画",
- "description": "设置抽奖通知窗口的动画",
- "switchbutton_name": {
- "enable": "启用",
- "disable": "禁用"
- }
+ "description": "设置抽奖通知窗口的显示动画效果",
+ "switchbutton_name": {"enable": "启用", "disable": "禁用"},
},
"enabled_monitor": {
- "name": "选择窗口显示的显示器",
- "description": "选择抽奖通知窗口显示的显示器"
+ "name": "选择抽奖通知显示的显示器",
+ "description": "选择抽奖通知窗口显示的显示器",
},
"window_position": {
"name": "窗口位置",
- "description": "设置抽奖通知窗口的位置",
+ "description": "设置抽奖通知窗口在屏幕上的显示位置",
"combo_items": [
"中心",
"顶部",
@@ -453,28 +423,28 @@
"顶部左侧",
"顶部右侧",
"底部左侧",
- "底部右侧"
- ]
+ "底部右侧",
+ ],
},
"horizontal_offset": {
"name": "水平偏移",
- "description": "设置抽奖通知窗口的水平偏移量(像素)"
+ "description": "设置抽奖通知窗口相对默认位置的水平偏移量(像素)",
},
"vertical_offset": {
"name": "垂直偏移",
- "description": "设置抽奖通知窗口的垂直偏移量(像素)"
+ "description": "设置抽奖通知窗口相对默认位置的垂直偏移量(像素)",
},
"transparency": {
"name": "透明度",
- "description": "设置抽奖通知窗口的透明度(0-100)"
+ "description": "设置抽奖通知窗口的透明度,数值越小越透明(0-100)",
},
"floating_window_enabled_monitor": {
- "name": "选择窗口显示的显示器",
- "description": "选择抽奖通知窗口显示的显示器"
- },
+ "name": "选择抽奖通知显示的显示器",
+ "description": "选择抽奖通知浮动窗口显示的显示器",
+ },
"floating_window_position": {
- "name": "窗口位置",
- "description": "设置抽奖通知窗口的位置",
+ "name": "浮动窗口位置",
+ "description": "设置抽奖通知浮动窗口在屏幕上的显示位置",
"combo_items": [
"顶部",
"底部",
@@ -483,20 +453,20 @@
"顶部左侧",
"顶部右侧",
"底部左侧",
- "底部右侧"
- ]
+ "底部右侧",
+ ],
},
"floating_window_horizontal_offset": {
"name": "水平偏移",
- "description": "设置抽奖通知窗口的水平偏移量(像素)"
+ "description": "设置抽奖通知浮动窗口相对默认位置的水平偏移量(像素)",
},
"floating_window_vertical_offset": {
"name": "垂直偏移",
- "description": "设置抽奖通知窗口的垂直偏移量(像素)"
+ "description": "设置抽奖通知浮动窗口相对默认位置的垂直偏移量(像素)",
},
"floating_window_transparency": {
"name": "浮动窗口透明度",
- "description": "设置抽奖通知窗口的透明度"
- }
+ "description": "设置抽奖通知浮动窗口的透明度,数值越小越透明(0-100)",
+ },
}
-}
\ No newline at end of file
+}
diff --git a/app/Language/modules/remaining_list.py b/app/Language/modules/remaining_list.py
new file mode 100644
index 00000000..ed37198e
--- /dev/null
+++ b/app/Language/modules/remaining_list.py
@@ -0,0 +1,20 @@
+# 剩余名单页面语言配置
+remaining_list = {
+ "ZH_CN": {
+ "title": {"name": "未抽取学生名单", "description": "剩余名单页面标题"},
+ "title_with_class": {
+ "name": "{class_name} - 未抽取学生名单",
+ "description": "带班级名称的标题",
+ },
+ "count_label": {"name": "剩余人数:{count}", "description": "剩余人数标签文本"},
+ "group_count_label": {"name": "剩余组数:{count}", "description": "剩余组数标签文本"},
+ "no_students": {
+ "name": "暂无未抽取的学生",
+ "description": "没有剩余学生时的提示文本",
+ },
+ "student_info": {
+ "name": "学号: {id}\n性别: {gender}\n小组: {group}",
+ "description": "学生卡片信息格式",
+ },
+ }
+}
diff --git a/app/Language/modules/roll_call_list.py b/app/Language/modules/roll_call_list.py
new file mode 100644
index 00000000..bcf4e9e3
--- /dev/null
+++ b/app/Language/modules/roll_call_list.py
@@ -0,0 +1,581 @@
+# 班级名称设置窗口
+set_class_name = {
+ "ZH_CN": {
+ "title": {"name": "班级名称设置", "description": "设置班级名称窗口标题"},
+ "description": {
+ "name": "在此窗口中,您可以设置班级名称信息\n每行输入将对应一个班级的名称,存储在班级名单文件中\n\n请每行输入一个班级名称,例如:\n高一1班\n高一2班\n高一3班",
+ "description": "班级名称设置窗口描述",
+ },
+ "input_title": {"name": "班级名称列表", "description": "班级名称输入区域标题"},
+ "input_placeholder": {
+ "name": "请输入班级名称,每行一个班级名称",
+ "description": "班级名称输入框占位符",
+ },
+ "save_button": {"name": "保存", "description": "保存按钮文本"},
+ "cancel_button": {"name": "取消", "description": "取消按钮文本"},
+ "error_title": {"name": "错误", "description": "错误消息标题"},
+ "success_title": {"name": "成功", "description": "成功消息标题"},
+ "info_title": {"name": "提示", "description": "信息消息标题"},
+ "no_class_names_error": {
+ "name": "请至少输入一个班级名称",
+ "description": "未输入班级名称时的错误提示",
+ },
+ "invalid_names_error": {
+ "name": "以下班级名称包含非法字符或是保留字: {names}",
+ "description": "班级名称验证失败时的错误提示",
+ },
+ "save_error": {
+ "name": "保存班级名称失败",
+ "description": "保存班级名称时的错误提示",
+ },
+ "success_message": {
+ "name": "成功创建 {count} 个新班级",
+ "description": "成功创建班级时的提示消息",
+ },
+ "no_new_classes_message": {
+ "name": "所有班级名称都已存在,没有创建新班级",
+ "description": "没有创建新班级时的提示消息",
+ },
+ "unsaved_changes_title": {
+ "name": "未保存的更改",
+ "description": "未保存更改对话框标题",
+ },
+ "unsaved_changes_message": {
+ "name": "您有未保存的更改,确定要关闭窗口吗?",
+ "description": "未保存更改对话框内容",
+ },
+ "discard_button": {"name": "放弃更改", "description": "放弃更改按钮文本"},
+ "continue_editing_button": {
+ "name": "继续编辑",
+ "description": "继续编辑按钮文本",
+ },
+ "delete_class_title": {"name": "删除班级", "description": "删除班级对话框标题"},
+ "delete_class_message": {
+ "name": "确定要删除班级 '{class_name}' 吗?此操作将删除该班级的所有学生数据,且无法恢复",
+ "description": "删除班级确认对话框内容",
+ },
+ "delete_class_button": {"name": "删除班级", "description": "删除班级按钮文本"},
+ "delete_multiple_classes_title": {
+ "name": "删除多个班级",
+ "description": "删除多个班级对话框标题",
+ },
+ "delete_multiple_classes_message": {
+ "name": "确定要删除以下 {count} 个班级吗?此操作将删除这些班级的所有学生数据,且无法恢复\n\n{class_names}",
+ "description": "删除多个班级确认对话框内容",
+ },
+ "delete_success_title": {"name": "删除成功", "description": "删除成功通知标题"},
+ "delete_success_message": {
+ "name": "成功删除 {count} 个班级",
+ "description": "删除成功通知内容",
+ },
+ "delete_cancel_button": {"name": "取消删除", "description": "取消删除按钮文本"},
+ "no_deletable_classes": {
+ "name": "没有可删除的班级",
+ "description": "没有可删除班级时的提示",
+ },
+ "select_class_to_delete": {
+ "name": "请选择要删除的班级",
+ "description": "选择删除班级的提示",
+ },
+ "select_class_dialog_title": {
+ "name": "选择要删除的班级",
+ "description": "选择删除班级对话框标题",
+ },
+ "select_class_dialog_message": {
+ "name": "请选择要删除的班级:",
+ "description": "选择删除班级对话框内容",
+ },
+ "delete_selected_button": {
+ "name": "删除选中",
+ "description": "删除选中按钮文本",
+ },
+ "delete_class_error": {
+ "name": "删除班级失败: {error}",
+ "description": "删除班级失败错误信息",
+ },
+ "class_disappeared_title": {
+ "name": "班级消失提示",
+ "description": "班级消失提示标题",
+ },
+ "class_disappeared_message": {
+ "name": "检测到班级 '{class_name}' 已从输入框中消失,请保存更改以永久删除",
+ "description": "单个班级消失提示内容",
+ },
+ "multiple_classes_disappeared_message": {
+ "name": "检测到以下 {count} 个班级已从输入框中消失,请保存更改以永久删除:\n{class_names}",
+ "description": "多个班级消失提示内容",
+ },
+ },
+}
+
+# 导入学生姓名语言配置
+import_student_name = {
+ "ZH_CN": {
+ "title": {
+ "name": "导入学生姓名",
+ "description": "从Excel或CSV文件导入学生姓名",
+ },
+ "initial_subtitle": {
+ "name": "正在导入到:",
+ "description": "正在导入到班级的提示",
+ },
+ "file_selection_title": {"name": "文件选择", "description": "文件选择区域标题"},
+ "no_file_selected": {
+ "name": "未选择文件",
+ "description": "未选择文件时的提示文本",
+ },
+ "select_file": {"name": "选择文件", "description": "选择文件按钮文本"},
+ "supported_formats": {
+ "name": "支持的格式: Excel (.xlsx, .xls) 和 CSV (.csv)",
+ "description": "支持的文件格式说明",
+ },
+ "file_filter": {
+ "name": "Excel 文件 (*.xlsx *.xls);;CSV 文件 (*.csv)",
+ "description": "文件选择对话框的文件过滤器",
+ },
+ "dialog_title": {"name": "选择文件", "description": "文件选择对话框标题"},
+ "column_mapping_title": {"name": "列映射", "description": "列映射区域标题"},
+ "column_mapping_description": {
+ "name": "请选择包含学生信息的列",
+ "description": "列映射区域说明",
+ },
+ "column_mapping_id_column": {
+ "name": "学号列 (必选):",
+ "description": "学号列标签",
+ },
+ "column_mapping_name_column": {
+ "name": "姓名列 (必选):",
+ "description": "姓名列标签",
+ },
+ "column_mapping_gender_column": {
+ "name": "性别列 (可选):",
+ "description": "性别列标签",
+ },
+ "column_mapping_group_column": {
+ "name": "小组列 (可选):",
+ "description": "小组列标签",
+ },
+ "column_mapping_none": {"name": "无", "description": "无选项文本"},
+ "data_preview_title": {"name": "数据预览", "description": "数据预览区域标题"},
+ "student_id": {"name": "学号", "description": "学号列标题"},
+ "name": {"name": "姓名", "description": "姓名列标题"},
+ "gender": {"name": "性别", "description": "性别列标题"},
+ "group": {"name": "小组", "description": "小组列标题"},
+ "buttons_import": {"name": "导入", "description": "导入按钮文本"},
+ "file_loaded_title": {
+ "name": "文件已加载",
+ "description": "文件加载成功对话框标题",
+ },
+ "file_loaded_content": {
+ "name": "文件加载成功",
+ "description": "文件加载成功对话框内容",
+ },
+ "file_loaded_notification_title": {
+ "name": "文件加载成功",
+ "description": "文件加载成功通知标题",
+ },
+ "file_loaded_notification_content": {
+ "name": "文件已成功加载,请检查数据预览",
+ "description": "文件加载成功通知内容",
+ },
+ "error_title": {"name": "错误", "description": "错误对话框标题"},
+ "load_failed": {"name": "加载文件失败", "description": "加载文件失败错误信息"},
+ "load_failed_notification_title": {
+ "name": "加载文件失败",
+ "description": "加载文件失败通知标题",
+ },
+ "load_failed_notification_content": {
+ "name": "无法加载文件,请检查文件格式和内容",
+ "description": "加载文件失败通知内容",
+ },
+ "import_failed": {
+ "name": "导入数据失败",
+ "description": "导入数据失败错误信息",
+ },
+ "import_failed_notification_title": {
+ "name": "导入数据失败",
+ "description": "导入数据失败通知标题",
+ },
+ "import_failed_notification_content": {
+ "name": "导入数据时发生错误,请检查数据格式和内容",
+ "description": "导入数据失败通知内容",
+ },
+ "unsupported_format": {
+ "name": "不支持的文件格式",
+ "description": "不支持的文件格式错误信息",
+ },
+ "no_name_column": {
+ "name": "请选择姓名列",
+ "description": "未选择姓名列错误信息",
+ },
+ "no_id_column": {"name": "请选择学号列", "description": "未选择学号列错误信息"},
+ "import_success_title": {
+ "name": "导入成功",
+ "description": "导入成功对话框标题",
+ },
+ "import_success_content_template": {
+ "name": "成功导入 {count} 个学生信息到班级 '{class_name}'",
+ "description": "导入成功对话框内容模板",
+ },
+ "import_success_notification_title": {
+ "name": "导入成功",
+ "description": "导入成功通知标题",
+ },
+ "import_success_notification_content_template": {
+ "name": "成功导入 {count} 个学生信息到班级 '{class_name}'",
+ "description": "导入成功通知内容模板",
+ },
+ "existing_data_title": {
+ "name": "班级已有数据",
+ "description": "班级已有数据对话框标题",
+ },
+ "existing_data_prompt": {
+ "name": "班级 '{class_name}' 已包含 {count} 名学生,请选择处理方式:",
+ "description": "班级已有数据对话框提示文本",
+ },
+ "existing_data_option_overwrite": {
+ "name": "覆盖现有数据",
+ "description": "覆盖现有数据选项",
+ },
+ "existing_data_option_cancel": {
+ "name": "取消导入",
+ "description": "取消导入选项",
+ },
+ }
+}
+
+# 姓名设置窗口
+name_setting = {
+ "ZH_CN": {
+ "title": {"name": "姓名设置", "description": "设置姓名窗口标题"},
+ "description": {
+ "name": "在此窗口中,您可以设置姓名信息\n每行输入将对应一个学生的姓名,存储在班级名单文件中\n\n请每行输入一个姓名,例如:\n张三\n李四\n王五",
+ "description": "姓名设置窗口描述",
+ },
+ "input_title": {"name": "姓名列表", "description": "姓名输入区域标题"},
+ "input_placeholder": {
+ "name": "请输入姓名,每行一个姓名",
+ "description": "姓名输入框占位符",
+ },
+ "save_button": {"name": "保存", "description": "保存按钮文本"},
+ "cancel_button": {"name": "取消", "description": "取消按钮文本"},
+ "error_title": {"name": "错误", "description": "错误消息标题"},
+ "success_title": {"name": "成功", "description": "成功消息标题"},
+ "info_title": {"name": "提示", "description": "信息消息标题"},
+ "no_names_error": {
+ "name": "请至少输入一个姓名",
+ "description": "未输入姓名时的错误提示",
+ },
+ "invalid_names_error": {
+ "name": "以下姓名包含非法字符或是保留字: {names}",
+ "description": "姓名验证失败时的错误提示",
+ },
+ "save_error": {"name": "保存姓名失败", "description": "保存姓名时的错误提示"},
+ "success_message": {
+ "name": "成功创建 {count} 个新姓名",
+ "description": "成功创建姓名时的提示消息",
+ },
+ "no_new_names_message": {
+ "name": "所有姓名都已存在,没有创建新姓名",
+ "description": "没有创建新姓名时的提示消息",
+ },
+ "unsaved_changes_title": {
+ "name": "未保存的更改",
+ "description": "未保存更改对话框标题",
+ },
+ "unsaved_changes_message": {
+ "name": "您有未保存的更改,确定要关闭窗口吗?",
+ "description": "未保存更改对话框内容",
+ },
+ "discard_button": {"name": "放弃更改", "description": "放弃更改按钮文本"},
+ "continue_editing_button": {
+ "name": "继续编辑",
+ "description": "继续编辑按钮文本",
+ },
+ "delete_button": {"name": "删除", "description": "删除按钮文本"},
+ "delete_name_title": {"name": "删除姓名", "description": "删除姓名对话框标题"},
+ "delete_name_message": {
+ "name": "确定要删除姓名 '{name}' 吗?此操作将删除该姓名的所有信息,且无法恢复",
+ "description": "删除姓名确认对话框内容",
+ },
+ "delete_multiple_names_title": {
+ "name": "删除多个姓名",
+ "description": "删除多个姓名对话框标题",
+ },
+ "delete_multiple_names_message": {
+ "name": "确定要删除以下 {count} 个姓名吗?此操作将删除这些姓名的所有信息,且无法恢复\n\n{names}",
+ "description": "删除多个姓名确认对话框内容",
+ },
+ "delete_name_success_title": {
+ "name": "删除成功",
+ "description": "删除姓名成功通知标题",
+ },
+ "delete_name_success_message": {
+ "name": "成功删除 {count} 个姓名",
+ "description": "删除姓名成功通知内容",
+ },
+ "delete_name_cancel_button": {
+ "name": "取消删除",
+ "description": "取消删除姓名按钮文本",
+ },
+ "no_deletable_names": {
+ "name": "没有可删除的姓名",
+ "description": "没有可删除姓名时的提示",
+ },
+ "select_name_to_delete": {
+ "name": "请选择要删除的姓名",
+ "description": "选择删除姓名的提示",
+ },
+ "select_name_dialog_title": {
+ "name": "选择要删除的姓名",
+ "description": "选择删除姓名对话框标题",
+ },
+ "select_name_dialog_message": {
+ "name": "请选择要删除的姓名:",
+ "description": "选择删除姓名对话框内容",
+ },
+ "delete_selected_names_button": {
+ "name": "删除选中",
+ "description": "删除选中姓名按钮文本",
+ },
+ "delete_name_error": {
+ "name": "删除姓名失败: {error}",
+ "description": "删除姓名失败错误信息",
+ },
+ "name_deleted_title": {"name": "姓名已删除", "description": "删除姓名提示标题"},
+ "name_deleted_message": {
+ "name": "姓名 '{name}' 已从输入框中删除,请保存更改以永久删除",
+ "description": "删除姓名提示内容",
+ },
+ },
+}
+
+# 性别设置窗口
+gender_setting = {
+ "ZH_CN": {
+ "title": {"name": "性别设置", "description": "设置性别窗口标题"},
+ "description": {
+ "name": "在此窗口中,您可以设置学生性别信息\n每行输入将对应一个学生的性别,存储在班级名单文件中\n\n请每行输入一个性别,例如:\n男\n女\n其他",
+ "description": "性别设置窗口描述",
+ },
+ "input_title": {"name": "性别列表", "description": "性别输入区域标题"},
+ "input_placeholder": {
+ "name": "请输入性别,每行一个性别",
+ "description": "性别输入框占位符",
+ },
+ "save_button": {"name": "保存", "description": "保存按钮文本"},
+ "cancel_button": {"name": "取消", "description": "取消按钮文本"},
+ "error_title": {"name": "错误", "description": "错误消息标题"},
+ "success_title": {"name": "成功", "description": "成功消息标题"},
+ "info_title": {"name": "提示", "description": "信息消息标题"},
+ "no_genders_error": {
+ "name": "请至少输入一个性别",
+ "description": "未输入性别时的错误提示",
+ },
+ "invalid_genders_error": {
+ "name": "以下性别包含非法字符或是保留字: {genders}",
+ "description": "性别验证失败时的错误提示",
+ },
+ "save_error": {
+ "name": "保存性别选项失败",
+ "description": "保存性别选项时的错误提示",
+ },
+ "success_message": {
+ "name": "成功创建 {count} 个新性别选项",
+ "description": "成功创建性别选项时的提示消息",
+ },
+ "no_new_genders_message": {
+ "name": "所有性别选项都已存在,没有创建新性别选项",
+ "description": "没有创建新性别选项时的提示消息",
+ },
+ "unsaved_changes_title": {
+ "name": "未保存的更改",
+ "description": "未保存更改对话框标题",
+ },
+ "unsaved_changes_message": {
+ "name": "您有未保存的更改,确定要关闭窗口吗?",
+ "description": "未保存更改对话框内容",
+ },
+ "discard_button": {"name": "放弃更改", "description": "放弃更改按钮文本"},
+ "continue_editing_button": {
+ "name": "继续编辑",
+ "description": "继续编辑按钮文本",
+ },
+ "delete_button": {"name": "删除", "description": "删除按钮文本"},
+ "delete_gender_title": {
+ "name": "删除性别选项",
+ "description": "删除性别选项对话框标题",
+ },
+ "delete_gender_message": {
+ "name": "确定要删除性别选项 '{gender}' 吗?此操作将删除该性别选项的所有信息,且无法恢复",
+ "description": "删除性别选项确认对话框内容",
+ },
+ "delete_multiple_genders_title": {
+ "name": "删除多个性别选项",
+ "description": "删除多个性别选项对话框标题",
+ },
+ "delete_multiple_genders_message": {
+ "name": "确定要删除以下 {count} 个性别选项吗?此操作将删除这些性别选项的所有信息,且无法恢复\n\n{genders}",
+ "description": "删除多个性别选项确认对话框内容",
+ },
+ "delete_gender_success_title": {
+ "name": "删除成功",
+ "description": "删除性别选项成功通知标题",
+ },
+ "delete_gender_success_message": {
+ "name": "成功删除 {count} 个性别选项",
+ "description": "删除性别选项成功通知内容",
+ },
+ "delete_gender_cancel_button": {
+ "name": "取消删除",
+ "description": "取消删除性别选项按钮文本",
+ },
+ "no_deletable_genders": {
+ "name": "没有可删除的性别选项",
+ "description": "没有可删除性别选项时的提示",
+ },
+ "select_gender_to_delete": {
+ "name": "请选择要删除的性别选项",
+ "description": "选择删除性别选项的提示",
+ },
+ "select_gender_dialog_title": {
+ "name": "选择要删除的性别选项",
+ "description": "选择删除性别选项对话框标题",
+ },
+ "select_gender_dialog_message": {
+ "name": "请选择要删除的性别选项:",
+ "description": "选择删除性别选项对话框内容",
+ },
+ "delete_selected_genders_button": {
+ "name": "删除选中",
+ "description": "删除选中性别选项按钮文本",
+ },
+ "delete_gender_error": {
+ "name": "删除性别选项失败: {error}",
+ "description": "删除性别选项失败错误信息",
+ },
+ "gender_deleted_title": {
+ "name": "性别选项已删除",
+ "description": "删除性别选项提示标题",
+ },
+ "gender_deleted_message": {
+ "name": "性别选项 '{gender}' 已从输入框中删除,请保存更改以永久删除",
+ "description": "删除性别选项提示内容",
+ },
+ },
+}
+
+# 小组设置窗口
+group_setting = {
+ "ZH_CN": {
+ "title": {"name": "小组设置", "description": "设置小组窗口标题"},
+ "description": {
+ "name": "在此窗口中,您可以设置学生小组信息\n每行输入将对应一个学生的小组,存储在班级名单文件中\n\n请每行输入一个小组,例如:\nA组\nB组\nC组",
+ "description": "小组设置窗口描述",
+ },
+ "input_title": {"name": "小组列表", "description": "小组输入区域标题"},
+ "input_placeholder": {
+ "name": "请输入小组,每行一个小组",
+ "description": "小组输入框占位符",
+ },
+ "save_button": {"name": "保存", "description": "保存按钮文本"},
+ "cancel_button": {"name": "取消", "description": "取消按钮文本"},
+ "error_title": {"name": "错误", "description": "错误消息标题"},
+ "success_title": {"name": "成功", "description": "成功消息标题"},
+ "info_title": {"name": "提示", "description": "信息消息标题"},
+ "no_groups_error": {
+ "name": "请至少输入一个小组",
+ "description": "未输入小组时的错误提示",
+ },
+ "invalid_groups_error": {
+ "name": "以下小组包含非法字符或是保留字: {groups}",
+ "description": "小组验证失败时的错误提示",
+ },
+ "save_error": {
+ "name": "保存小组选项失败",
+ "description": "保存小组选项时的错误提示",
+ },
+ "success_message": {
+ "name": "成功创建 {count} 个新小组选项",
+ "description": "成功创建小组选项时的提示消息",
+ },
+ "no_new_groups_message": {
+ "name": "所有小组选项都已存在,没有创建新小组选项",
+ "description": "没有创建新小组选项时的提示消息",
+ },
+ "unsaved_changes_title": {
+ "name": "未保存的更改",
+ "description": "未保存更改对话框标题",
+ },
+ "unsaved_changes_message": {
+ "name": "您有未保存的更改,确定要关闭窗口吗?",
+ "description": "未保存更改对话框内容",
+ },
+ "discard_button": {"name": "放弃更改", "description": "放弃更改按钮文本"},
+ "continue_editing_button": {
+ "name": "继续编辑",
+ "description": "继续编辑按钮文本",
+ },
+ "delete_button": {"name": "删除", "description": "删除按钮文本"},
+ "delete_group_title": {
+ "name": "删除小组选项",
+ "description": "删除小组选项对话框标题",
+ },
+ "delete_group_message": {
+ "name": "确定要删除小组选项 '{group}' 吗?此操作将删除该小组选项的所有信息,且无法恢复",
+ "description": "删除小组选项确认对话框内容",
+ },
+ "delete_multiple_groups_title": {
+ "name": "删除多个小组选项",
+ "description": "删除多个小组选项对话框标题",
+ },
+ "delete_multiple_groups_message": {
+ "name": "确定要删除以下 {count} 个小组选项吗?此操作将删除这些小组选项的所有信息,且无法恢复\n\n{groups}",
+ "description": "删除多个小组选项确认对话框内容",
+ },
+ "delete_group_success_title": {
+ "name": "删除成功",
+ "description": "删除小组选项成功通知标题",
+ },
+ "delete_group_success_message": {
+ "name": "成功删除 {count} 个小组选项",
+ "description": "删除小组选项成功通知内容",
+ },
+ "delete_group_cancel_button": {
+ "name": "取消删除",
+ "description": "取消删除小组选项按钮文本",
+ },
+ "no_deletable_groups": {
+ "name": "没有可删除的小组选项",
+ "description": "没有可删除小组选项时的提示",
+ },
+ "select_group_to_delete": {
+ "name": "请选择要删除的小组选项",
+ "description": "选择删除小组选项的提示",
+ },
+ "select_group_dialog_title": {
+ "name": "选择要删除的小组选项",
+ "description": "选择删除小组选项对话框标题",
+ },
+ "select_group_dialog_message": {
+ "name": "请选择要删除的小组选项:",
+ "description": "选择删除小组选项对话框内容",
+ },
+ "delete_selected_groups_button": {
+ "name": "删除选中",
+ "description": "删除选中小组选项按钮文本",
+ },
+ "delete_group_error": {
+ "name": "删除小组选项失败: {error}",
+ "description": "删除小组选项失败错误信息",
+ },
+ "group_deleted_title": {
+ "name": "小组选项已删除",
+ "description": "删除小组选项提示标题",
+ },
+ "group_deleted_message": {
+ "name": "小组选项 '{group}' 已从输入框中删除,请保存更改以永久删除",
+ "description": "删除小组选项提示内容",
+ },
+ },
+}
diff --git a/app/Language/modules/roll_call_main.py b/app/Language/modules/roll_call_main.py
index d7b4daf1..d5b59406 100644
--- a/app/Language/modules/roll_call_main.py
+++ b/app/Language/modules/roll_call_main.py
@@ -1,4 +1,46 @@
# 点名页面语言配置
roll_call = {
- "ZH_CN": {"title": {"name": "点名", "description": "点名"}}
-}
\ No newline at end of file
+ "ZH_CN": {
+ "title": {"name": "点名", "description": "点名"},
+ "reset_button": {
+ "name": "重置",
+ "description": "重置人数",
+ "pushbutton_name": "重置",
+ },
+ "start_button": {
+ "name": "开始",
+ "description": "开始点名",
+ "pushbutton_name": "开始",
+ },
+ "stop_button": {
+ "name": "停止",
+ "description": "停止点名",
+ "pushbutton_name": "停止",
+ },
+ "range_combobox": {
+ "name": "范围",
+ "description": "选择抽取范围",
+ "combo_items": ["抽取全部学生", "抽取全部小组"],
+ },
+ "gender_combobox": {
+ "name": "性别",
+ "description": "选择性别范围",
+ "combo_items": ["抽取全部性别"],
+ },
+ "remaining_button": {
+ "name": "查看剩余名单",
+ "description": "显示剩余名窗口单",
+ "pushbutton_name": "查看剩余名单",
+ },
+ "many_count_label": {
+ "name": "总/剩余人数",
+ "description": "显示总人数和剩余人数",
+ "text_0": "总人数: {total_count} | 剩余人数: {remaining_count}",
+ "text_1": "总人数: {total_count}",
+ "text_2": "剩余人数: {remaining_count}",
+ "text_3": "总组数: {total_count} | 剩余组数: {remaining_count}",
+ "text_4": "总组数: {total_count}",
+ "text_5": "剩余组数: {remaining_count}",
+ },
+ }
+}
diff --git a/app/Language/modules/safety_settings.py b/app/Language/modules/safety_settings.py
index a3faa340..3c67b4eb 100644
--- a/app/Language/modules/safety_settings.py
+++ b/app/Language/modules/safety_settings.py
@@ -1,64 +1,69 @@
# 安全设置语言配置
-safety_settings = {"ZH_CN": {"title": {"name": "安全设置", "description": "安全设置"}}}
+safety_settings = {
+ "ZH_CN": {"title": {"name": "安全设置", "description": "配置软件的安全相关设置"}}
+}
# 基础安全设置语言配置
basic_safety_settings = {
"ZH_CN": {
- "title": {"name": "基础安全设置", "description": "基础安全设置"},
+ "title": {"name": "基础安全设置", "description": "配置基础安全验证功能"},
"verification_method": {
"name": "验证方式",
- "description": "设置安全功能的验证方式",
+ "description": "配置安全功能的验证方式",
},
"verification_process": {
"name": "安全验证步骤",
- "description": "设置安全功能的验证步骤",
+ "description": "选择安全验证的组合方式",
"combo_items": [
"单步验证(任选一种方式)",
- "仅 密码",
- "仅 TOTP",
- "仅 U盘解锁",
- "密码 + TOTP",
- "密码 + U盘解锁",
- "TOTP + U盘解锁",
- "密码 + TOTP + U盘解锁",
+ "仅密码",
+ "仅TOTP",
+ "仅U盘解锁",
+ "密码+TOTP",
+ "密码+U盘解锁",
+ "TOTP+U盘解锁",
+ "密码+TOTP+U盘解锁",
],
},
"security_operations": {
"name": "安全操作",
- "description": "设置安全操作的验证方式",
+ "description": "配置需要安全验证的操作",
},
"safety_switch": {
"name": "安全开关",
- "description": "开启后,所有带有安全操作的功能都需要验证密码",
+ "description": "启用后,所有安全操作都需要验证密码",
"switchbutton_name": {"enable": "启用", "disable": "禁用"},
},
- "set_password": {"name": "设置/修改密码", "description": "设置或修改安全密码"},
+ "set_password": {
+ "name": "设置/修改密码",
+ "description": "设置或修改安全验证密码",
+ },
"totp_switch": {
- "name": "是否开启TOTP开关",
- "description": "开启后,将可以在安全操作中使用TOTP",
+ "name": "TOTP验证",
+ "description": "启用后可在安全操作中使用TOTP动态口令",
"switchbutton_name": {"enable": "启用", "disable": "禁用"},
},
- "set_totp": {"name": "设置TOTP", "description": "设置TOTP"},
+ "set_totp": {"name": "设置TOTP", "description": "配置TOTP动态口令验证"},
"usb_switch": {
- "name": "是否开启U盘开关",
- "description": "开启后,将可以在安全操作中使用U盘解锁",
+ "name": "U盘验证",
+ "description": "启用后可在安全操作中使用U盘验证",
"switchbutton_name": {"enable": "启用", "disable": "禁用"},
},
- "bind_usb": {"name": "绑定U盘", "description": "绑定U盘解锁"},
- "unbind_usb": {"name": "解绑U盘", "description": "解绑U盘解锁"},
+ "bind_usb": {"name": "绑定U盘", "description": "绑定用于验证的U盘设备"},
+ "unbind_usb": {"name": "解绑U盘", "description": "解除U盘设备的绑定"},
"show_hide_floating_window_switch": {
- "name": "显示/隐藏操作是否需安全验证",
- "description": "开启后可在安全操作中显示/隐藏悬浮窗,需安全验证",
+ "name": "显示/隐藏浮窗验证",
+ "description": "启用后显示或隐藏浮窗时需要安全验证",
"switchbutton_name": {"enable": "启用", "disable": "禁用"},
},
"restart_switch": {
- "name": "重启操作是否需安全验证",
- "description": "开启后重启软件,需安全验证",
+ "name": "重启验证",
+ "description": "启用后重启软件时需要安全验证",
"switchbutton_name": {"enable": "启用", "disable": "禁用"},
},
"exit_switch": {
- "name": "退出操作是否需安全验证",
- "description": "开启后退出软件,需安全验证",
+ "name": "退出验证",
+ "description": "启用后退出软件时需要安全验证",
"switchbutton_name": {"enable": "启用", "disable": "禁用"},
},
}
diff --git a/app/Language/modules/sidebar_settings.py b/app/Language/modules/sidebar_settings.py
index a6549230..48f8d4fc 100644
--- a/app/Language/modules/sidebar_settings.py
+++ b/app/Language/modules/sidebar_settings.py
@@ -3,56 +3,56 @@
"ZH_CN": {
"title": {
"name": "设置窗口侧边栏",
- "description": "配置设置窗口侧边栏管理设置",
+ "description": "配置设置窗口的侧边栏",
},
"home": {
"name": "首页位置",
- "description": "配置侧边栏管理首页位置",
+ "description": "设置首页在侧边栏中的位置",
"combo_items": ["顶部", "底部", "不显示"],
},
"base_settings": {
"name": "基础设置",
- "description": "配置基础设置",
+ "description": "设置基础设置项在侧边栏中的位置",
"combo_items": ["顶部", "底部", "不显示"],
},
"name_management": {
"name": "名单管理",
- "description": "配置名单管理",
+ "description": "设置名单管理项在侧边栏中的位置",
"combo_items": ["顶部", "底部", "不显示"],
},
"draw_settings": {
"name": "抽取设置",
- "description": "配置抽取设置",
+ "description": "设置抽取设置项在侧边栏中的位置",
"combo_items": ["顶部", "底部", "不显示"],
},
"notification_service": {
"name": "通知服务",
- "description": "配置通知服务",
+ "description": "设置通知服务项在侧边栏中的位置",
"combo_items": ["顶部", "底部", "不显示"],
},
"security_settings": {
"name": "安全设置",
- "description": "配置安全设置",
+ "description": "设置安全设置项在侧边栏中的位置",
"combo_items": ["顶部", "底部", "不显示"],
},
"personal_settings": {
"name": "个性设置",
- "description": "配置个性设置",
+ "description": "设置个性设置项在侧边栏中的位置",
"combo_items": ["顶部", "底部"],
},
"voice_settings": {
"name": "语音设置",
- "description": "配置语音设置",
+ "description": "设置语音设置项在侧边栏中的位置",
"combo_items": ["顶部", "底部", "不显示"],
},
"settings_history": {
"name": "设置界面历史记录",
- "description": "配置设置界面历史记录",
+ "description": "设置历史记录项在侧边栏中的位置",
"combo_items": ["顶部", "底部", "不显示"],
},
"more_settings": {
"name": "更多设置",
- "description": "配置更多设置",
+ "description": "设置更多设置项在侧边栏中的位置",
"combo_items": ["顶部", "底部", "不显示"],
},
}
@@ -61,30 +61,30 @@
# 托盘管理语言配置
tray_management = {
"ZH_CN": {
- "title": {"name": "托盘管理", "description": "配置托盘管理相关设置"},
+ "title": {"name": "托盘管理", "description": "配置系统托盘相关设置"},
"show_hide_main_window": {
"name": "暂时显示/隐藏主界面",
- "description": "配置暂时显示/隐藏主界面",
+ "description": "控制主界面的显示和隐藏",
"switchbutton_name": {"enable": "显示", "disable": "隐藏"},
},
"open_settings": {
"name": "打开设置窗口",
- "description": "配置打开设置窗口",
+ "description": "控制是否在托盘菜单中显示设置窗口选项",
"switchbutton_name": {"enable": "显示", "disable": "隐藏"},
},
"show_hide_float_window": {
"name": "暂时显示/隐藏浮窗",
- "description": "配置暂时显示/隐藏浮窗",
+ "description": "控制浮动窗口的显示和隐藏",
"switchbutton_name": {"enable": "显示", "disable": "隐藏"},
},
"restart": {
"name": "重启应用",
- "description": "配置重启应用",
+ "description": "控制是否在托盘菜单中显示重启选项",
"switchbutton_name": {"enable": "重启", "disable": "不重启"},
},
"exit": {
"name": "退出应用",
- "description": "配置退出应用",
+ "description": "控制是否在托盘菜单中显示退出选项",
"switchbutton_name": {"enable": "退出", "disable": "不退出"},
},
}
diff --git a/app/Language/modules/sidebar_tray_management.py b/app/Language/modules/sidebar_tray_management.py
index fbf2fbab..66390de3 100644
--- a/app/Language/modules/sidebar_tray_management.py
+++ b/app/Language/modules/sidebar_tray_management.py
@@ -1,62 +1,71 @@
# 浮窗管理语言配置
floating_window_management = {
"ZH_CN": {
- "title": {"name": "浮窗管理", "description": "配置浮窗管理相关设置"},
- "basic_settings": {"name": "基本设置", "description": "配置浮窗管理基本设置"},
+ "title": {"name": "浮窗管理", "description": "配置浮动窗口相关设置"},
+ "basic_settings": {"name": "基本设置", "description": "配置浮动窗口的基本设置"},
"appearance_settings": {
"name": "外观设置",
- "description": "配置浮窗管理外观设置",
+ "description": "配置浮动窗口的外观设置",
},
- "edge_settings": {"name": "贴边设置", "description": "配置浮窗管理贴边设置"},
+ "edge_settings": {"name": "贴边设置", "description": "配置浮动窗口的贴边设置"},
"startup_display_floating_window": {
"name": "软件启动时显示浮窗",
- "description": "配置软件启动时是否显示浮窗",
+ "description": "控制软件启动时是否自动显示浮动窗口",
"switchbutton_name": {"enable": "显示", "disable": "隐藏"},
},
"floating_window_opacity": {
"name": "浮窗透明度",
- "description": "配置浮窗透明度",
+ "description": "调整浮动窗口的透明度",
},
"reset_floating_window_position_button": {
"name": "重置浮窗位置按钮",
- "description": "点击后将浮窗位置重置为默认位置",
+ "description": "将浮动窗口位置重置为默认位置",
"pushbutton_name": "重置位置",
},
"floating_window_button_control": {
"name": "浮窗控件配置",
- "description": "配置浮窗中显示的按钮",
+ "description": "选择在浮动窗口中显示的功能按钮",
"combo_items": [
- "点名", "闪抽", "即抽", "自定义抽", "抽奖",
- "点名+闪抽", "点名+自定义抽", "点名+抽奖",
- "闪抽+自定义抽", "闪抽+抽奖",
+ "点名",
+ "闪抽",
+ "即抽",
+ "自定义抽",
+ "抽奖",
+ "点名+闪抽",
+ "点名+自定义抽",
+ "点名+抽奖",
+ "闪抽+自定义抽",
+ "闪抽+抽奖",
"自定义抽+抽奖",
- "点名+闪抽+自定义抽", "点名+闪抽+抽奖", "点名+自定义抽+抽奖",
+ "点名+闪抽+自定义抽",
+ "点名+闪抽+抽奖",
+ "点名+自定义抽+抽奖",
"闪抽+自定义抽+抽奖",
- "点名+闪抽+自定义抽+抽奖"
+ "点名+闪抽+自定义抽+抽奖",
],
},
"floating_window_placement": {
"name": "浮窗排列",
- "description": "配置浮窗控件排列",
+ "description": "设置浮动窗口中控件的排列方式",
"combo_items": ["矩形排列", "竖向排列", "横向排列"],
},
"floating_window_display_style": {
"name": "浮窗显示样式",
- "description": "配置浮窗控件显示样式",
+ "description": "设置浮动窗口中控件的显示样式",
"combo_items": ["图标+文字", "图标", "文字"],
},
"floating_window_stick_to_edge": {
"name": "贴边设置",
- "description": "配置浮窗是否贴边",
+ "description": "控制浮动窗口是否自动贴边",
"switchbutton_name": {"enable": "贴边", "disable": "不贴边"},
},
"floating_window_stick_to_edge_recover_seconds": {
"name": "贴边收纳时间",
- "description": "配置浮窗贴边后收纳时间",
+ "description": "设置浮动窗口贴边后自动收纳的时间(秒)",
},
"floating_window_stick_to_edge_display_style": {
"name": "贴边显示样式",
- "description": "配置浮窗贴边后显示样式",
+ "description": "设置浮动窗口贴边时的显示样式",
"combo_items": ["图标", "文字", "箭头"],
},
}
diff --git a/app/Language/modules/voice_settings.py b/app/Language/modules/voice_settings.py
index ea8316b5..593cae37 100644
--- a/app/Language/modules/voice_settings.py
+++ b/app/Language/modules/voice_settings.py
@@ -1,26 +1,29 @@
# 语音设置语言配置
voice_settings = {
- "ZH_CN": {"title": {"name": "语音设置", "description": "语音设置页面"}}
+ "ZH_CN": {"title": {"name": "语音设置", "description": "配置语音播报相关功能"}}
}
# 基础语音设置语言配置
basic_voice_settings = {
"ZH_CN": {
- "title": {"name": "基础语音设置", "description": "基础语音设置"},
- "voice_engine_group": {"name": "语音引擎", "description": "选择语音合成引擎"},
- "volume_group": {"name": "音量设置", "description": "设置语音播放的音量大小"},
+ "title": {"name": "基础语音设置", "description": "配置基础语音播报功能"},
+ "voice_engine_group": {
+ "name": "语音引擎",
+ "description": "选择语音合成引擎类型",
+ },
+ "volume_group": {"name": "音量设置", "description": "调整语音播报的音量大小"},
"system_volume_group": {
"name": "系统音量控制",
"description": "选择要控制的系统音量类型",
},
"voice_engine": {
"name": "语音引擎",
- "description": "选择语音合成引擎",
+ "description": "选择语音合成引擎类型",
"combo_items": ["系统TTS", "Edge TTS"],
},
"edge_tts_voice_name": {
"name": "Edge TTS-语音名称",
- "description": "选择Edge TTS语音",
+ "description": "选择Edge TTS语音播报角色",
"combo_items": [
"zh-CN-XiaoxiaoNeural",
"zh-CN-YunxiNeural",
@@ -30,12 +33,12 @@
],
},
"voice_playback": {
- "name": "语音播放",
- "description": "选择语音播放设备",
+ "name": "语音播放设备",
+ "description": "选择语音播报的播放设备",
"combo_items": ["系统默认", "扬声器", "耳机", "蓝牙设备"],
},
- "volume_size": {"name": "音量大小", "description": "设置语音播放的音量大小"},
- "speech_rate": {"name": "语速调节", "description": "设置语音播放的语速"},
+ "volume_size": {"name": "播报音量", "description": "调整语音播报的音量大小"},
+ "speech_rate": {"name": "语速调节", "description": "调整语音播报的语速"},
"system_volume_control": {
"name": "系统音量控制",
"description": "选择要控制的系统音量类型",
@@ -43,7 +46,7 @@
},
"system_volume_size": {
"name": "系统音量大小",
- "description": "设置系统音量大小",
+ "description": "调整系统音量的大小",
},
}
}
diff --git a/app/Language/obtain_language.py b/app/Language/obtain_language.py
index facbca91..b78031c5 100644
--- a/app/Language/obtain_language.py
+++ b/app/Language/obtain_language.py
@@ -4,30 +4,59 @@
"""
该模块包含所有应用程序内容文本的默认值配置
使用层级结构组织内容文本项,第一层为分类,第二层为具体内容文本项
+
+便捷函数总结:
+1. 同步函数:
+ - get_content_name(first_level_key, second_level_key): 获取内容文本项的名称
+ - get_content_description(first_level_key, second_level_key): 获取内容文本项的描述
+ - get_content_pushbutton_name(first_level_key, second_level_key): 获取内容文本项的按钮名称
+ - get_content_switchbutton_name_async(first_level_key, second_level_key, is_enable): 获取内容文本项的开关按钮名称
+ - get_content_combo_name_async(first_level_key, second_level_key): 获取内容文本项的下拉框内容
+ - get_any_position_value(first_level_key, second_level_key, *keys): 根据层级键获取任意位置的值
+
+2. 异步函数:
+ - get_content_name_async(first_level_key, second_level_key, timeout=1000): 异步获取内容文本项的名称
+ - get_content_description_async(first_level_key, second_level_key, timeout=1000): 异步获取内容文本项的描述
+ - get_content_pushbutton_name_async(first_level_key, second_level_key, timeout=1000): 异步获取内容文本项的按钮名称
+ - get_content_switchbutton_name_async(first_level_key, second_level_key, is_enable, timeout=1000): 异步获取内容文本项的开关按钮名称
+ - get_content_combo_name_async(first_level_key, second_level_key, timeout=1000): 异步获取内容文本项的下拉框内容
+ - get_any_position_value_async(first_level_key, second_level_key, *keys, timeout=1000): 异步根据层级键获取任意位置的值
+
+注意:所有异步函数在失败时会自动回退到对应的同步方法,确保功能稳定性。
"""
+
from app.tools.language_manager import *
from app.tools.settings_access import *
from loguru import logger
-from typing import Any, Optional
# 获取语言数据
Language = get_current_language_data()
+
# ==================================================
# 异步语言读取工作线程
# ==================================================
class LanguageReaderWorker(QObject):
"""语言读取工作线程"""
- finished = pyqtSignal(object) # 信号,传递读取结果
-
- def __init__(self, first_level_key: str, second_level_key: str, value_type: str = "name", *keys):
+
+ finished = Signal(object) # 信号,传递读取结果
+
+ def __init__(
+ self,
+ first_level_key: str,
+ second_level_key: str,
+ value_type: str = "name",
+ *keys,
+ ):
super().__init__()
self.first_level_key = first_level_key
self.second_level_key = second_level_key
- self.value_type = value_type # 可以是 "name", "description", "pushbutton_name" 等
+ self.value_type = (
+ value_type # 可以是 "name", "description", "pushbutton_name" 等
+ )
self.keys = keys # 后续任意层级的键
-
+
def run(self):
"""执行语言读取操作"""
try:
@@ -36,21 +65,21 @@ def run(self):
except Exception as e:
logger.error(f"读取语言内容失败: {e}")
self.finished.emit(None)
-
+
def _read_language_value(self):
"""从语言数据中读取值"""
# 获取最新的语言数据
language_data = get_current_language_data()
-
+
# 如果是获取任意位置的值
if self.value_type == "any_position":
return self._get_any_position_value(language_data)
-
+
# 检查键是否存在
if self.first_level_key in language_data:
if self.second_level_key in language_data[self.first_level_key]:
item_data = language_data[self.first_level_key][self.second_level_key]
-
+
# 根据类型返回不同的值
if self.value_type == "name":
return item_data.get("name")
@@ -64,9 +93,9 @@ def _read_language_value(self):
return item_data.get("switchbutton_name", {})
else:
return item_data
-
+
return None
-
+
def _get_any_position_value(self, language_data):
"""获取任意位置的值"""
if self.first_level_key in language_data:
@@ -81,14 +110,22 @@ def _get_any_position_value(self, language_data):
return current
return None
+
class AsyncLanguageReader(QObject):
"""异步语言读取器,提供简洁的异步读取方式"""
-
+
# 定义信号
- finished = pyqtSignal(object) # 读取完成信号,携带结果
- error = pyqtSignal(str) # 错误信号
-
- def __init__(self, first_level_key: str, second_level_key: str, value_type: str = "name", switch_state: str = None, *keys):
+ finished = Signal(object) # 读取完成信号,携带结果
+ error = Signal(str) # 错误信号
+
+ def __init__(
+ self,
+ first_level_key: str,
+ second_level_key: str,
+ value_type: str = "name",
+ switch_state: str = None,
+ *keys,
+ ):
super().__init__()
self.first_level_key = first_level_key
self.second_level_key = second_level_key
@@ -100,82 +137,89 @@ def __init__(self, first_level_key: str, second_level_key: str, value_type: str
self._result = None
self._completed = False
self._future = None
-
+
def read_async(self):
"""异步读取语言内容,返回Future对象"""
# 创建工作线程
self.thread = QThread()
- self.worker = LanguageReaderWorker(self.first_level_key, self.second_level_key, self.value_type, *self.keys)
+ self.worker = LanguageReaderWorker(
+ self.first_level_key, self.second_level_key, self.value_type, *self.keys
+ )
self.worker.moveToThread(self.thread)
-
+
# 连接信号
self.thread.started.connect(self.worker.run)
self.worker.finished.connect(self._handle_result)
self.worker.finished.connect(self.worker.deleteLater)
self.thread.finished.connect(self.thread.deleteLater)
-
+
# 创建Future对象
self._future = asyncio.Future()
-
+
# 启动线程
self.thread.start()
-
+
# 返回Future对象
return self._future
-
+
def result(self, timeout=None):
"""等待并返回结果,类似Future的result()方法"""
if self._completed:
return self._result
-
+
if self.thread and self.thread.isRunning():
if timeout is not None:
self.thread.wait(timeout)
else:
self.thread.wait()
-
+
return self._result
-
+
def is_done(self):
"""检查是否已完成"""
return self._completed
-
+
def _handle_result(self, value):
"""处理语言读取结果"""
# 对于开关按钮,需要进一步处理
- if self.value_type.startswith("switchbutton_name") and self.switch_state and isinstance(value, dict):
+ if (
+ self.value_type.startswith("switchbutton_name")
+ and self.switch_state
+ and isinstance(value, dict)
+ ):
self._result = value.get(self.switch_state)
else:
self._result = value
-
+
self._completed = True
-
+
# 设置Future结果
if self._future and not self._future.done():
self._future.set_result(self._result)
-
+
# 发出完成信号
self.finished.emit(self._result)
-
+
# 安全地清理线程
self._cleanup_thread()
-
+
def _cleanup_thread(self):
"""安全地清理线程资源"""
if self.thread and self.thread.isRunning():
self.thread.quit()
self.thread.wait(1000) # 最多等待1秒线程结束
+
# ==================================================
# 便捷函数
# ==================================================
def get_content_name(first_level_key: str, second_level_key: str):
"""根据键获取内容文本项的名称
-
+
Args:
first_level_key: 第一层的键
second_level_key: 第二层的键
-
+
Returns:
内容文本项的名称,如果不存在则返回None
"""
@@ -185,13 +229,14 @@ def get_content_name(first_level_key: str, second_level_key: str):
return Language[first_level_key][second_level_key]["name"]
return None
+
def get_content_description(first_level_key: str, second_level_key: str):
"""根据键获取内容文本项的描述
-
+
Args:
first_level_key: 第一层的键
second_level_key: 第二层的键
-
+
Returns:
内容文本项的描述,如果不存在则返回None
"""
@@ -201,13 +246,14 @@ def get_content_description(first_level_key: str, second_level_key: str):
return Language[first_level_key][second_level_key]["description"]
return None
+
def get_content_pushbutton_name(first_level_key: str, second_level_key: str):
"""根据键获取内容文本项的按钮名称
-
+
Args:
first_level_key: 第一层的键
second_level_key: 第二层的键
-
+
Returns:
内容文本项的按钮名称,如果不存在则返回None
"""
@@ -217,30 +263,36 @@ def get_content_pushbutton_name(first_level_key: str, second_level_key: str):
return Language[first_level_key][second_level_key]["pushbutton_name"]
return None
-def get_content_switchbutton_name_async(first_level_key: str, second_level_key: str, is_enable: str):
+
+def get_content_switchbutton_name(
+ first_level_key: str, second_level_key: str, is_enable: str
+):
"""根据键获取内容文本项的开关按钮名称
-
+
Args:
first_level_key: 第一层的键
second_level_key: 第二层的键
is_enable: 是否启用开关按钮("enable"或"disable")
-
+
Returns:
内容文本项的开关按钮名称,如果不存在则返回None
"""
if first_level_key in Language:
if second_level_key in Language[first_level_key]:
# logger.debug(f"获取内容文本项开关按钮名称: {first_level_key}.{second_level_key}")
- return Language[first_level_key][second_level_key]["switchbutton_name"][is_enable]
+ return Language[first_level_key][second_level_key]["switchbutton_name"][
+ is_enable
+ ]
return None
-def get_content_combo_name_async(first_level_key: str, second_level_key: str):
+
+def get_content_combo_name(first_level_key: str, second_level_key: str):
"""根据键获取内容文本项的下拉框内容
-
+
Args:
first_level_key: 第一层的键
second_level_key: 第二层的键
-
+
Returns:
内容文本项的下拉框内容,如果不存在则返回None
"""
@@ -250,14 +302,15 @@ def get_content_combo_name_async(first_level_key: str, second_level_key: str):
return Language[first_level_key][second_level_key]["combo_items"]
return None
+
def get_any_position_value(first_level_key: str, second_level_key: str, *keys):
"""根据层级键获取任意位置的值
-
+
Args:
first_level_key: 第一层的键
second_level_key: 第二层的键
*keys: 后续任意层级的键
-
+
Returns:
指定位置的值,如果不存在则返回None
"""
@@ -274,271 +327,116 @@ def get_any_position_value(first_level_key: str, second_level_key: str, *keys):
return current
return None
+
# ==================================================
# 异步版本的语言获取函数
# ==================================================
def get_content_name_async(first_level_key: str, second_level_key: str, timeout=1000):
- """异步获取内容文本项的名称,如果失败则回退到同步方法
-
+ """异步获取内容名称(简化版:直接调用同步方法)
+
+ 为保持 API 兼容性而保留,但在 Nuitka 环境下 QTimer 有兼容性问题,
+ 因此直接使用同步方法。实际测试表明同步方法性能已足够好。
+
Args:
first_level_key (str): 第一层的键
second_level_key (str): 第二层的键
- timeout (int, optional): 异步超时时间(毫秒),默认1000ms
-
+ timeout (int, optional): 保留参数,用于兼容性
+
Returns:
Any: 内容文本项的名称
-
- Example:
- # 直接获取结果,内部自动处理异步和回退
- name = get_content_name_async("appearance", "theme")
"""
- try:
- # 尝试异步读取
- reader = AsyncLanguageReader(first_level_key, second_level_key, "name")
- future = reader.read_async()
-
- # 等待结果,带超时
- loop = QEventLoop()
- timeout_timer = QTimer()
- timeout_timer.singleShot(timeout, loop.quit)
-
- # 连接完成信号
- reader.finished.connect(loop.quit)
- reader.error.connect(loop.quit)
-
- # 执行事件循环
- loop.exec()
-
- # 检查是否已完成
- if reader.is_done():
- # logger.debug(f"异步获取内容名称 {first_level_key}.{second_level_key} 成功: {reader.result()}")
- return reader.result()
- else:
- logger.warning(f"异步获取内容名称 {first_level_key}.{second_level_key} 超时,回退到同步方法")
- return get_content_name(first_level_key, second_level_key)
-
- except Exception as e:
- logger.warning(f"异步获取内容名称 {first_level_key}.{second_level_key} 失败: {e},回退到同步方法")
- return get_content_name(first_level_key, second_level_key)
-
-def get_content_description_async(first_level_key: str, second_level_key: str, timeout=1000):
- """异步获取内容文本项的描述,如果失败则回退到同步方法
-
+ return get_content_name(first_level_key, second_level_key)
+
+
+def get_content_description_async(
+ first_level_key: str, second_level_key: str, timeout=1000
+):
+ """异步获取内容描述(简化版:直接调用同步方法)
+
+ 为保持 API 兼容性而保留,但在 Nuitka 环境下 QTimer 有兼容性问题,
+ 因此直接使用同步方法。
+
Args:
first_level_key (str): 第一层的键
second_level_key (str): 第二层的键
- timeout (int, optional): 异步超时时间(毫秒),默认1000ms
-
+ timeout (int, optional): 保留参数,用于兼容性
+
Returns:
Any: 内容文本项的描述
-
- Example:
- # 直接获取结果,内部自动处理异步和回退
- description = get_content_description_async("appearance", "theme")
"""
- try:
- # 尝试异步读取
- reader = AsyncLanguageReader(first_level_key, second_level_key, "description")
- future = reader.read_async()
-
- # 等待结果,带超时
- loop = QEventLoop()
- timeout_timer = QTimer()
- timeout_timer.singleShot(timeout, loop.quit)
-
- # 连接完成信号
- reader.finished.connect(loop.quit)
- reader.error.connect(loop.quit)
-
- # 执行事件循环
- loop.exec()
-
- # 检查是否已完成
- if reader.is_done():
- # logger.debug(f"异步获取内容描述 {first_level_key}.{second_level_key} 成功: {reader.result()}")
- return reader.result()
- else:
- logger.warning(f"异步获取内容描述 {first_level_key}.{second_level_key} 超时,回退到同步方法")
- return get_content_description(first_level_key, second_level_key)
-
- except Exception as e:
- logger.warning(f"异步获取内容描述 {first_level_key}.{second_level_key} 失败: {e},回退到同步方法")
- return get_content_description(first_level_key, second_level_key)
-
-def get_content_pushbutton_name_async(first_level_key: str, second_level_key: str, timeout=1000):
- """异步获取内容文本项的按钮名称,如果失败则回退到同步方法
-
+ return get_content_description(first_level_key, second_level_key)
+
+
+def get_content_pushbutton_name_async(
+ first_level_key: str, second_level_key: str, timeout=1000
+):
+ """异步获取按钮名称(简化版:直接调用同步方法)
+
+ 为保持 API 兼容性而保留,但在 Nuitka 环境下 QTimer 有兼容性问题,
+ 因此直接使用同步方法。
+
Args:
first_level_key (str): 第一层的键
second_level_key (str): 第二层的键
- timeout (int, optional): 异步超时时间(毫秒),默认1000ms
-
+ timeout (int, optional): 保留参数,用于兼容性
+
Returns:
Any: 内容文本项的按钮名称
-
- Example:
- # 直接获取结果,内部自动处理异步和回退
- button_name = get_content_pushbutton_name_async("appearance", "theme")
"""
- try:
- # 尝试异步读取
- reader = AsyncLanguageReader(first_level_key, second_level_key, "pushbutton_name")
- future = reader.read_async()
-
- # 等待结果,带超时
- loop = QEventLoop()
- timeout_timer = QTimer()
- timeout_timer.singleShot(timeout, loop.quit)
-
- # 连接完成信号
- reader.finished.connect(loop.quit)
- reader.error.connect(loop.quit)
-
- # 执行事件循环
- loop.exec()
-
- # 检查是否已完成
- if reader.is_done():
- # logger.debug(f"异步获取按钮名称 {first_level_key}.{second_level_key} 成功: {reader.result()}")
- return reader.result()
- else:
- logger.warning(f"异步获取按钮名称 {first_level_key}.{second_level_key} 超时,回退到同步方法")
- return get_content_pushbutton_name(first_level_key, second_level_key)
-
- except Exception as e:
- logger.warning(f"异步获取按钮名称 {first_level_key}.{second_level_key} 失败: {e},回退到同步方法")
- return get_content_pushbutton_name(first_level_key, second_level_key)
-
-def get_content_switchbutton_name_async(first_level_key: str, second_level_key: str, is_enable: str, timeout=1000):
- """异步获取内容文本项的开关按钮名称,如果失败则回退到同步方法
-
+ return get_content_pushbutton_name(first_level_key, second_level_key)
+
+
+def get_content_switchbutton_name_async(
+ first_level_key: str, second_level_key: str, is_enable: str, timeout=1000
+):
+ """异步获取开关按钮名称(简化版:直接调用同步方法)
+
+ 为保持 API 兼容性而保留,但在 Nuitka 环境下 QTimer 有兼容性问题,
+ 因此直接使用同步方法。
+
Args:
first_level_key (str): 第一层的键
second_level_key (str): 第二层的键
is_enable (str): 是否启用开关按钮("enable"或"disable")
- timeout (int, optional): 异步超时时间(毫秒),默认1000ms
-
+ timeout (int, optional): 保留参数,用于兼容性
+
Returns:
Any: 内容文本项的开关按钮名称
-
- Example:
- # 直接获取结果,内部自动处理异步和回退
- switch_name = get_content_switchbutton_name_async("appearance", "theme", "enable")
"""
- try:
- # 尝试异步读取
- reader = AsyncLanguageReader(first_level_key, second_level_key, "switchbutton_name", is_enable)
- future = reader.read_async()
-
- # 等待结果,带超时
- loop = QEventLoop()
- timeout_timer = QTimer()
- timeout_timer.singleShot(timeout, loop.quit)
-
- # 连接完成信号
- reader.finished.connect(loop.quit)
- reader.error.connect(loop.quit)
-
- # 执行事件循环
- loop.exec()
-
- # 检查是否已完成
- if reader.is_done():
- # logger.debug(f"异步获取开关按钮名称 {first_level_key}.{second_level_key} 成功: {reader.result()}")
- return reader.result()
- else:
- logger.warning(f"异步获取开关按钮名称 {first_level_key}.{second_level_key} 超时,回退到同步方法")
- return get_content_switchbutton_name_async(first_level_key, second_level_key, is_enable)
-
- except Exception as e:
- logger.warning(f"异步获取开关按钮名称 {first_level_key}.{second_level_key} 失败: {e},回退到同步方法")
- return get_content_switchbutton_name_async(first_level_key, second_level_key, is_enable)
-
-def get_content_combo_name_async(first_level_key: str, second_level_key: str, timeout=1000):
- """异步获取内容文本项的下拉框内容,如果失败则回退到同步方法
-
+ return get_content_switchbutton_name(first_level_key, second_level_key, is_enable)
+
+
+def get_content_combo_name_async(
+ first_level_key: str, second_level_key: str, timeout=1000
+):
+ """异步获取内容文本项的下拉框内容(简化版:直接调用同步方法)
+
+ Nuitka 打包环境下 QTimer 的签名检查会导致 singleShot 抛出异常,因此
+ 这里直接复用同步版本。保留 timeout 参数以兼容旧调用。
+
Args:
first_level_key (str): 第一层的键
second_level_key (str): 第二层的键
- timeout (int, optional): 异步超时时间(毫秒),默认1000ms
-
+ timeout (int, optional): 保留参数,用于兼容性
+
Returns:
Any: 内容文本项的下拉框内容
-
- Example:
- # 直接获取结果,内部自动处理异步和回退
- combo_items = get_content_combo_name_async("appearance", "theme")
"""
- try:
- # 尝试异步读取
- reader = AsyncLanguageReader(first_level_key, second_level_key, "combo_items")
- future = reader.read_async()
-
- # 等待结果,带超时
- loop = QEventLoop()
- timeout_timer = QTimer()
- timeout_timer.singleShot(timeout, loop.quit)
-
- # 连接完成信号
- reader.finished.connect(loop.quit)
- reader.error.connect(loop.quit)
-
- # 执行事件循环
- loop.exec()
-
- # 检查是否已完成
- if reader.is_done():
- # logger.debug(f"异步获取下拉框内容 {first_level_key}.{second_level_key} 成功: {reader.result()}")
- return reader.result()
- else:
- logger.warning(f"异步获取下拉框内容 {first_level_key}.{second_level_key} 超时,回退到同步方法")
- return get_content_combo_name_async(first_level_key, second_level_key)
-
- except Exception as e:
- logger.warning(f"异步获取下拉框内容 {first_level_key}.{second_level_key} 失败: {e},回退到同步方法")
- return get_content_combo_name_async(first_level_key, second_level_key)
-
-def get_any_position_value_async(first_level_key: str, second_level_key: str, *keys, timeout=1000):
- """异步根据层级键获取任意位置的值,如果失败则回退到同步方法
-
+ return get_content_combo_name(first_level_key, second_level_key)
+
+
+def get_any_position_value_async(
+ first_level_key: str, second_level_key: str, *keys, timeout=1000
+):
+ """异步获取任意层级值(简化版:直接调用同步方法)
+
Args:
first_level_key (str): 第一层的键
second_level_key (str): 第二层的键
*keys: 后续任意层级的键
- timeout (int, optional): 异步超时时间(毫秒),默认1000ms
-
+ timeout (int, optional): 保留参数,用于兼容性
+
Returns:
Any: 指定位置的值,如果不存在则返回None
-
- Example:
- # 直接获取结果,内部自动处理异步和回退
- value = get_any_position_value_async("appearance", "theme", "color", "primary")
"""
- try:
- # 尝试异步读取
- reader = AsyncLanguageReader(first_level_key, second_level_key, "any_position", None, *keys)
- future = reader.read_async()
-
- # 等待结果,带超时
- loop = QEventLoop()
- timeout_timer = QTimer()
- timeout_timer.singleShot(timeout, loop.quit)
-
- # 连接完成信号
- reader.finished.connect(loop.quit)
- reader.error.connect(loop.quit)
-
- # 执行事件循环
- loop.exec()
-
- # 检查是否已完成
- if reader.is_done():
- # logger.debug(f"异步获取任意位置值 {first_level_key}.{second_level_key} 成功: {reader.result()}")
- return reader.result()
- else:
- logger.warning(f"异步获取任意位置值 {first_level_key}.{second_level_key} 超时,回退到同步方法")
- return get_any_position_value(first_level_key, second_level_key, *keys)
-
- except Exception as e:
- logger.warning(f"异步获取任意位置值 {first_level_key}.{second_level_key} 失败: {e},回退到同步方法")
- return get_any_position_value(first_level_key, second_level_key, *keys)
\ No newline at end of file
+ return get_any_position_value(first_level_key, second_level_key, *keys)
diff --git a/app/__init__.py b/app/__init__.py
new file mode 100644
index 00000000..6b42d8d2
--- /dev/null
+++ b/app/__init__.py
@@ -0,0 +1 @@
+"""SecRandom application package."""
diff --git a/app/common/another_window.py b/app/common/another_window.py
index a8e6b66a..675173b4 100644
--- a/app/common/another_window.py
+++ b/app/common/another_window.py
@@ -1,10 +1,7 @@
# ==================================================
# 导入库
# ==================================================
-import json
import os
-import sys
-import subprocess
from loguru import logger
from PyQt6.QtWidgets import *
@@ -12,7 +9,6 @@
from PyQt6.QtCore import *
from PyQt6.QtNetwork import *
from qfluentwidgets import *
-from qfluentwidgets import FluentIcon as FIF
from app.tools.variable import *
from app.tools.path_utils import *
@@ -34,7 +30,7 @@ def __init__(self, parent=None):
self.setMinimumSize(900, 600)
self.setSizeGripEnabled(True) #启用右下角拖动柄
self.update_theme_style()
-
+
self.saved = False
self.dragging = False
self.drag_position = None
@@ -42,26 +38,26 @@ def __init__(self, parent=None):
# 确保不设置子窗口的屏幕属性
if parent is not None:
self.setParent(parent)
-
+
# 创建自定义标题栏
self.title_bar = QWidget()
self.title_bar.setObjectName("CustomTitleBar")
self.title_bar.setFixedHeight(35)
-
+
# 标题栏布局
title_layout = QHBoxLayout(self.title_bar)
title_layout.setContentsMargins(10, 0, 10, 0)
-
+
# 窗口标题
self.title_label = BodyLabel(get_content_name_async("about", "contributor"))
self.title_label.setObjectName("TitleLabel")
-
+
# 窗口控制按钮
self.close_btn = QPushButton("✕")
self.close_btn.setObjectName("CloseButton")
self.close_btn.setFixedSize(25, 25)
self.close_btn.clicked.connect(self.reject)
-
+
# 添加组件到标题栏
title_layout.addWidget(self.title_label)
title_layout.addStretch()
@@ -83,7 +79,7 @@ def __init__(self, parent=None):
}
""")
scroll.setWidget(content)
-
+
# 主布局
self.layout = QVBoxLayout(self)
self.layout.setContentsMargins(0, 0, 0, 0)
@@ -95,10 +91,10 @@ def __init__(self, parent=None):
content_layout.addWidget(scroll)
content_layout.setContentsMargins(0, 0, 0, 0)
self.layout.addLayout(content_layout)
-
+
self.update_theme_style()
qconfig.themeChanged.connect(self.update_theme_style)
-
+
# 贡献者数据
contributors = [
{
@@ -151,7 +147,7 @@ def __init__(self, parent=None):
'avatar': str(get_resources_path('assets/contribution', 'contributor8.png'))
}
]
-
+
# 计算所有职责文本的行数,让它们变得整齐划一
fm = QFontMetrics(self.font())
max_lines = 0
@@ -186,7 +182,7 @@ def __init__(self, parent=None):
for contributor in contributors:
card = self.addContributorCard(contributor)
self.cards.append(card)
-
+
self.update_layout()
def update_layout(self):
@@ -196,7 +192,7 @@ def update_layout(self):
widget = item.widget()
if widget:
widget.hide()
-
+
# 响应式布局配置
CARD_MIN_WIDTH = 250 # 卡片最小宽度
MAX_COLUMNS = 12 # 最大列数限制
@@ -212,7 +208,7 @@ def calculate_columns(width):
# 根据窗口宽度计算列数
cols = calculate_columns(self.width())
-
+
# 添加卡片到网格
for i, card in enumerate(self.cards):
row = i // cols
@@ -247,62 +243,62 @@ def update_theme_style(self):
is_dark = lightness <= 127
else:
is_dark = qconfig.theme == Theme.DARK
-
+
# 主题样式更新
colors = {'text': '#F5F5F5', 'bg': '#111116', 'title_bg': '#2D2D2D'} if is_dark else {'text': '#111116', 'bg': '#F5F5F5', 'title_bg': '#E0E0E0'}
self.setStyleSheet(f"""
QDialog {{ background-color: {colors['bg']}; border-radius: 5px; }}
#CustomTitleBar {{ background-color: {colors['title_bg']}; }}
#TitleLabel {{ color: {colors['text']}; font-weight: bold; padding: 5px; }}
- #CloseButton {{
- background-color: transparent;
- color: {colors['text']};
- border-radius: 4px;
- font-weight: bold;
+ #CloseButton {{
+ background-color: transparent;
+ color: {colors['text']};
+ border-radius: 4px;
+ font-weight: bold;
border: none;
}}
- #CloseButton:hover {{
- background-color: #ff4d4d;
- color: white;
+ #CloseButton:hover {{
+ background-color: #ff4d4d;
+ color: white;
border: none;
}}
QLabel, QPushButton, QTextEdit {{ color: {colors['text']}; }}
- QLineEdit {{
- background-color: {colors['bg']};
- color: {colors['text']};
- border: 1px solid #555555;
- border-radius: 4px;
- padding: 5px;
+ QLineEdit {{
+ background-color: {colors['bg']};
+ color: {colors['text']};
+ border: 1px solid #555555;
+ border-radius: 4px;
+ padding: 5px;
}}
- QPushButton {{
- background-color: {colors['bg']};
- color: {colors['text']};
- border: 1px solid #555555;
- border-radius: 4px;
- padding: 5px;
+ QPushButton {{
+ background-color: {colors['bg']};
+ color: {colors['text']};
+ border: 1px solid #555555;
+ border-radius: 4px;
+ padding: 5px;
}}
QPushButton:hover {{ background-color: #606060; }}
- QComboBox {{
- background-color: {colors['bg']};
- color: {colors['text']};
- border: 1px solid #555555;
- border-radius: 4px;
- padding: 5px;
+ QComboBox {{
+ background-color: {colors['bg']};
+ color: {colors['text']};
+ border: 1px solid #555555;
+ border-radius: 4px;
+ padding: 5px;
}}
""")
-
+
# 设置标题栏颜色以匹配背景色(仅Windows系统)
if os.name == 'nt':
try:
import ctypes
# 修复参数类型错误
hwnd = int(self.winId()) # 转换为整数句柄
-
+
# 颜色格式要改成ARGB格式
bg_color = colors['bg'].lstrip('#')
# 转换为ARGB格式(添加不透明通道)
rgb_color = int(f'FF{bg_color}', 16) if len(bg_color) == 6 else int(bg_color, 16)
-
+
# 设置窗口标题栏颜色
ctypes.windll.dwmapi.DwmSetWindowAttribute(
ctypes.c_int(hwnd), # 窗口句柄(整数类型)
@@ -320,7 +316,7 @@ def closeEvent(self, event):
w.cancelButton.setText("取消")
w.yesButton = PrimaryPushButton('确定')
w.cancelButton = PushButton('取消')
-
+
if w.exec():
self.reject()
return
@@ -328,7 +324,7 @@ def closeEvent(self, event):
event.ignore()
return
event.accept()
-
+
def update_card_theme_style(self, card):
"""根据当前主题更新样式"""
if qconfig.theme == Theme.AUTO:
@@ -347,7 +343,7 @@ def update_card_theme_style(self, card):
margin-bottom: 10px;
}}
''')
-
+
def addContributorCard(self, contributor):
""" 添加贡献者卡片 """
card = QWidget()
@@ -378,4 +374,4 @@ def addContributorCard(self, contributor):
role.setMaximumWidth(500)
cardLayout.addWidget(role, 0, Qt.AlignmentFlag.AlignCenter)
- return card
\ No newline at end of file
+ return card
diff --git a/app/common/config.py b/app/common/config.py
index 7bdb34ad..bc1f0d25 100644
--- a/app/common/config.py
+++ b/app/common/config.py
@@ -1,25 +1,24 @@
# ==================================================
# 导入模块
# ==================================================
-import json
import sys
-from qfluentwidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtWidgets import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
+from qfluentwidgets import *
+from PySide6.QtGui import *
+from PySide6.QtWidgets import *
+from PySide6.QtCore import *
+from PySide6.QtNetwork import *
from loguru import logger
# 平台特定导入
-if sys.platform == 'win32':
+if sys.platform == "win32":
try:
from pycaw.pycaw import AudioUtilities, IAudioEndpointVolume
import comtypes
from comtypes import POINTER
except ImportError:
pass # Windows libraries not available
-elif sys.platform.startswith('linux'):
+elif sys.platform.startswith("linux"):
try:
import pulsectl
except ImportError:
@@ -28,31 +27,33 @@
from app.tools.variable import *
from app.tools.path_utils import *
+
# ==================================================
# 系统功能相关函数
# ==================================================
def restore_volume(volume_value):
"""跨平台音量恢复函数
-
+
Args:
volume_value (int): 音量值 (0-100)
"""
- if sys.platform == 'win32':
+ if sys.platform == "win32":
# Windows音频控制
try:
# 初始化COM库
comtypes.CoInitialize()
-
+
# 获取默认音频设备
devices = AudioUtilities.GetSpeakers()
interface = devices.Activate(
- IAudioEndpointVolume._iid_, comtypes.CLSCTX_ALL, None)
+ IAudioEndpointVolume._iid_, comtypes.CLSCTX_ALL, None
+ )
volume = comtypes.cast(interface, POINTER(IAudioEndpointVolume))
-
+
try:
# 取消静音
volume.SetMute(0, None)
-
+
# 设置音量
volume.SetMasterVolumeLevelScalar(volume_value / 100.0, None)
logger.info(f"Windows音量设置为: {volume_value}%")
@@ -62,42 +63,42 @@ def restore_volume(volume_value):
volume = None
interface = None
devices = None
-
+
# 释放COM库
comtypes.CoUninitialize()
except Exception as e:
logger.error(f"Windows音量控制失败: {e}")
- elif sys.platform.startswith('linux'):
+ elif sys.platform.startswith("linux"):
# Linux音频控制 (使用PulseAudio)
try:
if pulsectl is None:
logger.warning("pulsectl未安装,无法控制音量")
return
-
- with pulsectl.Pulse('secrandom-volume-control') as pulse:
+
+ with pulsectl.Pulse("secrandom-volume-control") as pulse:
# 获取默认sink(输出设备)
sinks = pulse.sink_list()
if not sinks:
logger.warning("未找到音频输出设备")
return
-
+
# 获取默认sink或第一个可用的sink
default_sink = None
for sink in sinks:
if sink.name == pulse.server_info().default_sink_name:
default_sink = sink
break
-
+
if default_sink is None:
default_sink = sinks[0]
-
+
# 取消静音
pulse.sink_mute(default_sink.index, 0)
-
+
# 设置音量 (PulseAudio使用0.0-1.0范围)
pulse.volume_set_all_chans(default_sink, volume_value / 100.0)
logger.info(f"Linux音量设置为: {volume_value}%")
except Exception as e:
logger.error(f"Linux音量控制失败: {e}")
else:
- logger.warning(f"不支持的平台: {sys.platform},音量控制功能不可用")
\ No newline at end of file
+ logger.warning(f"不支持的平台: {sys.platform},音量控制功能不可用")
diff --git a/app/page_building/another_window.py b/app/page_building/another_window.py
new file mode 100644
index 00000000..da6d2c1d
--- /dev/null
+++ b/app/page_building/another_window.py
@@ -0,0 +1,309 @@
+# 导入页面模板
+from PySide6.QtCore import QTimer
+from app.page_building.page_template import PageTemplate
+from app.page_building.window_template import SimpleWindowTemplate
+from app.view.another_window.contributor import contributor_page
+from app.view.another_window.import_student_name import ImportStudentNameWindow
+from app.view.another_window.set_class_name import SetClassNameWindow
+from app.view.another_window.name_setting import NameSettingWindow
+from app.view.another_window.gender_setting import GenderSettingWindow
+from app.view.another_window.group_setting import GroupSettingWindow
+from app.view.another_window.remaining_list import RemainingListPage
+from app.Language.obtain_language import *
+
+# 全局变量,用于保持窗口引用,防止被垃圾回收
+_window_instances = {}
+
+
+# ==================================================
+# 班级名称设置窗口
+# ==================================================
+class set_class_name_window_template(PageTemplate):
+ """班级名称设置窗口类
+ 使用PageTemplate创建班级名称设置页面"""
+
+ def __init__(self, parent=None):
+ super().__init__(content_widget_class=SetClassNameWindow, parent=parent)
+
+
+def create_set_class_name_window():
+ """
+ 创建班级名称设置窗口
+
+ Returns:
+ 创建的窗口实例
+ """
+ title = get_content_name_async("set_class_name", "title")
+ window = SimpleWindowTemplate(title, width=800, height=600)
+ window.add_page_from_template("set_class_name", set_class_name_window_template)
+ window.switch_to_page("set_class_name")
+ _window_instances["set_class_name"] = window
+ window.windowClosed.connect(lambda: _window_instances.pop("set_class_name", None))
+ window.show()
+ return
+
+
+# ==================================================
+# 导入学生名单导入窗口
+# ==================================================
+class import_student_name_window_template(PageTemplate):
+ """学生名单导入窗口类
+ 使用PageTemplate创建学生名单导入页面"""
+
+ def __init__(self, parent=None):
+ super().__init__(content_widget_class=ImportStudentNameWindow, parent=parent)
+
+
+def create_import_student_name_window():
+ """
+ 创建学生名单导入窗口
+
+ Returns:
+ 创建的窗口实例
+ """
+ title = get_content_name_async("import_student_name", "title")
+ window = SimpleWindowTemplate(title, width=800, height=600)
+ window.add_page_from_template(
+ "import_student_name", import_student_name_window_template
+ )
+ window.switch_to_page("import_student_name")
+ _window_instances["import_student_name"] = window
+ window.windowClosed.connect(
+ lambda: _window_instances.pop("import_student_name", None)
+ )
+ window.show()
+ return
+
+
+# ==================================================
+# 姓名设置窗口
+# ==================================================
+class name_setting_window_template(PageTemplate):
+ """姓名设置窗口类
+ 使用PageTemplate创建姓名设置页面"""
+
+ def __init__(self, parent=None):
+ super().__init__(content_widget_class=NameSettingWindow, parent=parent)
+
+
+def create_name_setting_window():
+ """
+ 创建姓名设置窗口
+
+ Returns:
+ 创建的窗口实例
+ """
+ title = get_content_name_async("name_setting", "title")
+ window = SimpleWindowTemplate(title, width=800, height=600)
+ window.add_page_from_template("name_setting", name_setting_window_template)
+ window.switch_to_page("name_setting")
+ _window_instances["name_setting"] = window
+ window.windowClosed.connect(lambda: _window_instances.pop("name_setting", None))
+ window.show()
+ return
+
+
+# ==================================================
+# 性别设置窗口
+# ==================================================
+class gender_setting_window_template(PageTemplate):
+ """性别设置窗口类
+ 使用PageTemplate创建性别设置页面"""
+
+ def __init__(self, parent=None):
+ super().__init__(content_widget_class=GenderSettingWindow, parent=parent)
+
+
+def create_gender_setting_window():
+ """
+ 创建性别设置窗口
+
+ Returns:
+ 创建的窗口实例
+ """
+ title = get_content_name_async("gender_setting", "title")
+ window = SimpleWindowTemplate(title, width=800, height=600)
+ window.add_page_from_template("gender_setting", gender_setting_window_template)
+ window.switch_to_page("gender_setting")
+ _window_instances["gender_setting"] = window
+ window.windowClosed.connect(lambda: _window_instances.pop("gender_setting", None))
+ window.show()
+ return
+
+
+# ==================================================
+# 小组设置窗口
+# ==================================================
+class group_setting_window_template(PageTemplate):
+ """小组设置窗口类
+ 使用PageTemplate创建小组设置页面"""
+
+ def __init__(self, parent=None):
+ super().__init__(content_widget_class=GroupSettingWindow, parent=parent)
+
+
+def create_group_setting_window():
+ """
+ 创建小组设置窗口
+
+ Returns:
+ 创建的窗口实例
+ """
+ title = get_content_name_async("group_setting", "title")
+ window = SimpleWindowTemplate(title, width=800, height=600)
+ window.add_page_from_template("group_setting", group_setting_window_template)
+ window.switch_to_page("group_setting")
+ _window_instances["group_setting"] = window
+ window.windowClosed.connect(lambda: _window_instances.pop("group_setting", None))
+ window.show()
+ return
+
+
+# ==================================================
+# 贡献者窗口
+# ==================================================
+class contributor_window_template(PageTemplate):
+ """贡献者窗口类
+ 使用PageTemplate创建贡献者页面"""
+
+ def __init__(self, parent=None):
+ super().__init__(content_widget_class=contributor_page, parent=parent)
+
+
+def create_contributor_window():
+ """
+ 创建贡献者窗口
+
+ Returns:
+ 创建的窗口实例
+ """
+ title = get_content_name_async("about", "contributor")
+ window = SimpleWindowTemplate(title, width=800, height=600)
+ window.add_page_from_template("contributor", contributor_window_template)
+ window.switch_to_page("contributor")
+ _window_instances["contributor"] = window
+ window.windowClosed.connect(lambda: _window_instances.pop("contributor", None))
+ window.show()
+ return
+
+
+# ==================================================
+# 剩余名单窗口
+# ==================================================
+class remaining_list_window_template(PageTemplate):
+ """剩余名单窗口类
+ 使用PageTemplate创建剩余名单页面"""
+
+ def __init__(self, parent=None):
+ super().__init__(content_widget_class=RemainingListPage, parent=parent)
+
+
+def create_remaining_list_window(
+ class_name: str,
+ group_filter: str,
+ gender_filter: str,
+ half_repeat: int = 0,
+ group_index: int = 0,
+ gender_index: int = 0,
+):
+ """
+ 创建剩余名单窗口
+
+ Args:
+ class_name: 班级名称
+ group_filter: 分组筛选条件
+ gender_filter: 性别筛选条件
+ half_repeat: 重复抽取次数
+ group_index: 分组索引
+ gender_index: 性别索引
+
+ Returns:
+ 创建的窗口实例和页面实例
+ """
+ # 检查是否已存在剩余名单窗口
+ if "remaining_list" in _window_instances:
+ window = _window_instances["remaining_list"]
+ try:
+ # 激活窗口并置于前台
+ window.raise_()
+ window.activateWindow()
+
+ # 获取页面实例并更新数据
+ page = None
+
+ def setup_page():
+ nonlocal page
+ page_template = window.get_page("remaining_list")
+ if page_template and hasattr(page_template, "contentWidget"):
+ page = page_template.contentWidget
+ if hasattr(page, "update_remaining_list"):
+ page.update_remaining_list(
+ class_name,
+ group_filter,
+ gender_filter,
+ half_repeat,
+ group_index,
+ gender_index,
+ )
+
+ # 使用延迟调用确保内容控件已创建
+ QTimer.singleShot(100, setup_page)
+
+ # 创建一个回调函数,用于在页面设置完成后获取页面实例
+ def get_page_callback(callback):
+ def check_page():
+ if page is not None:
+ callback(page)
+ else:
+ QTimer.singleShot(50, check_page)
+
+ check_page()
+
+ return window, get_page_callback
+ except Exception as e:
+ # 如果窗口已损坏,从字典中移除并创建新窗口
+ logger.error(f"激活剩余名单窗口失败: {e}")
+ _window_instances.pop("remaining_list", None)
+
+ # 创建新窗口
+ title = "剩余名单"
+ window = SimpleWindowTemplate(title, width=800, height=600)
+ window.add_page_from_template("remaining_list", remaining_list_window_template)
+ window.switch_to_page("remaining_list")
+
+ # 获取页面实例并更新数据
+ page = None
+
+ def setup_page():
+ nonlocal page
+ page_template = window.get_page("remaining_list")
+ if page_template and hasattr(page_template, "contentWidget"):
+ page = page_template.contentWidget
+ if hasattr(page, "update_remaining_list"):
+ page.update_remaining_list(
+ class_name,
+ group_filter,
+ gender_filter,
+ half_repeat,
+ group_index,
+ gender_index,
+ )
+
+ # 使用延迟调用确保内容控件已创建
+ QTimer.singleShot(100, setup_page)
+
+ _window_instances["remaining_list"] = window
+ window.windowClosed.connect(lambda: _window_instances.pop("remaining_list", None))
+ window.show()
+
+ # 创建一个回调函数,用于在页面设置完成后获取页面实例
+ def get_page_callback(callback):
+ def check_page():
+ if page is not None:
+ callback(page)
+ else:
+ QTimer.singleShot(50, check_page)
+
+ check_page()
+
+ return window, get_page_callback
diff --git a/app/page_building/main_window_page.py b/app/page_building/main_window_page.py
index 362a37e6..1cee92c8 100644
--- a/app/page_building/main_window_page.py
+++ b/app/page_building/main_window_page.py
@@ -1,5 +1,5 @@
# 导入库
-from PyQt6.QtWidgets import QFrame
+from PySide6.QtWidgets import QFrame
# 导入页面模板
from app.page_building.page_template import PageTemplate
@@ -7,7 +7,9 @@
# 导入自定义页面内容组件
from app.view.main.roll_call import roll_call
+
class roll_call_page(PageTemplate):
"""创建班级点名页面"""
+
def __init__(self, parent: QFrame = None):
super().__init__(content_widget_class=roll_call, parent=parent)
diff --git a/app/page_building/page_template.py b/app/page_building/page_template.py
index a965b0c7..3e854349 100644
--- a/app/page_building/page_template.py
+++ b/app/page_building/page_template.py
@@ -1,18 +1,15 @@
# ==================================================
# 导入库
# ==================================================
-import json
-import os
-import sys
-import subprocess
-import random
import importlib
+import time
from loguru import logger
-from PyQt6.QtWidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
+
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from PySide6.QtNetwork import *
from qfluentwidgets import *
from app.tools.variable import *
@@ -26,26 +23,26 @@ class PageTemplate(QFrame):
def __new__(cls, content_widget_class=None, parent: QFrame = None):
# 直接创建新实例,不使用缓存
return super(PageTemplate, cls).__new__(cls)
-
+
def __init__(self, content_widget_class=None, parent: QFrame = None):
super().__init__(parent=parent)
-
+
self.ui_created = False
self.content_created = False
self.content_widget_class = content_widget_class
-
+
self.__connectSignalToSlot()
-
+
QTimer.singleShot(0, self.create_ui_components)
def __connectSignalToSlot(self):
qconfig.themeChanged.connect(setTheme)
-
+
def create_ui_components(self):
"""后台创建UI组件,避免堵塞进程"""
if self.ui_created:
return
-
+
self.scroll_area_personal = SingleDirectionScrollArea(self)
self.scroll_area_personal.setWidgetResizable(True)
self.scroll_area_personal.setStyleSheet("""
@@ -58,148 +55,182 @@ def create_ui_components(self):
background-color: transparent;
}
""")
- QScroller.grabGesture(self.scroll_area_personal.viewport(), QScroller.ScrollerGestureType.LeftMouseButtonGesture)
+ QScroller.grabGesture(
+ self.scroll_area_personal.viewport(),
+ QScroller.ScrollerGestureType.LeftMouseButtonGesture,
+ )
self.inner_frame_personal = QWidget(self.scroll_area_personal)
self.inner_layout_personal = QVBoxLayout(self.inner_frame_personal)
- self.inner_layout_personal.setAlignment(Qt.AlignmentFlag.AlignCenter | Qt.AlignmentFlag.AlignTop)
+ self.inner_layout_personal.setAlignment(
+ Qt.AlignmentFlag.AlignCenter | Qt.AlignmentFlag.AlignTop
+ )
self.scroll_area_personal.setWidget(self.inner_frame_personal)
self.main_layout = QVBoxLayout(self)
self.main_layout.addWidget(self.scroll_area_personal)
-
+
self.ui_created = True
-
+
if self.content_widget_class:
QTimer.singleShot(0, self.create_content)
-
+
def create_content(self):
"""后台创建内容组件,避免堵塞进程"""
if not self.ui_created or self.content_created or not self.content_widget_class:
return
-
- self.contentWidget = self.content_widget_class(self)
- self.inner_layout_personal.addWidget(self.contentWidget)
- self.content_created = True
-
+
+ # 支持传入三种类型的 content_widget_class:
+ # 1) 直接的类 / 可调用对象 -> content_widget_class(self)
+ # 2) 字符串形式的导入路径,如 'app.view.settings.home:home' 或 'app.view.settings.home.home'
+ # -> 动态导入模块并获取类
+ start = time.perf_counter()
+ try:
+ content_cls = None
+ content_name = None
+ if isinstance(self.content_widget_class, str):
+ path = self.content_widget_class
+ content_name = path
+ if ":" in path:
+ module_name, attr = path.split(":", 1)
+ else:
+ module_name, attr = path.rsplit(".", 1)
+ module = importlib.import_module(module_name)
+ content_cls = getattr(module, attr)
+ else:
+ content_cls = self.content_widget_class
+ content_name = getattr(content_cls, "__name__", str(content_cls))
+
+ # 实例化并添加到布局
+ self.contentWidget = content_cls(self)
+ self.inner_layout_personal.addWidget(self.contentWidget)
+ self.content_created = True
+
+ elapsed = time.perf_counter() - start
+ logger.debug(f"创建内容组件 {content_name} 耗时: {elapsed:.3f}s")
+ except Exception as e:
+ elapsed = time.perf_counter() - start
+ logger.error(f"创建内容组件失败 ({elapsed:.3f}s): {e}")
+
def create_empty_content(self, message="该页面正在开发中,敬请期待!"):
"""创建空页面内容"""
if self.content_created:
return
-
+
empty_widget = QWidget()
empty_layout = QVBoxLayout(empty_widget)
-
+
center_container = QWidget()
center_layout = QVBoxLayout(center_container)
center_layout.setAlignment(Qt.AlignCenter)
center_layout.setSpacing(20)
-
+
if message:
custom_label = BodyLabel(message)
custom_label.setAlignment(Qt.AlignCenter)
custom_label.setFont(QFont(load_custom_font(), 12))
center_layout.addWidget(custom_label)
-
+
empty_layout.addWidget(center_container)
empty_layout.addStretch()
-
+
self.contentWidget = empty_widget
self.inner_layout_personal.addWidget(self.contentWidget)
self.content_created = True
-
+
@classmethod
def clear_instance_cache(cls):
"""清除实例缓存,用于强制重新创建页面"""
cls._instances.clear()
-
+
@classmethod
def remove_instance(cls, content_widget_class=None, parent=None):
"""移除特定实例"""
if content_widget_class is None:
- content_class_name = 'None'
+ content_class_name = "None"
else:
- if hasattr(content_widget_class, '__name__'):
+ if hasattr(content_widget_class, "__name__"):
content_class_name = content_widget_class.__name__
else:
content_class_name = str(type(content_widget_class).__name__)
-
- parent_id = id(parent) if parent else 'None'
+
+ parent_id = id(parent) if parent else "None"
instance_key = f"{cls.__name__}_{content_class_name}_{parent_id}"
-
+
if instance_key in cls._instances:
del cls._instances[instance_key]
+
class PivotPageTemplate(QFrame):
"""Pivot 导航页面模板类,支持动态加载不同的页面组件"""
-
+
def __init__(self, page_config: dict, parent: QFrame = None):
"""
初始化 Pivot 页面模板
-
+
Args:
page_config: 页面配置字典,格式为 {"page_name": "display_name", ...}
parent: 父窗口
"""
super().__init__(parent=parent)
-
+
self.page_config = page_config # 页面配置字典
self.ui_created = False
self.pages = {} # 存储页面组件
self.current_page = None # 当前页面
self.base_path = "app.view.settings.list_management" # 默认基础路径
-
+
self.__connectSignalToSlot()
-
+
QTimer.singleShot(0, self.create_ui_components)
-
+
def __connectSignalToSlot(self):
"""连接信号与槽"""
qconfig.themeChanged.connect(setTheme)
-
+
def create_ui_components(self):
"""创建UI组件"""
if self.ui_created:
return
-
+
# 创建主布局
self.main_layout = QVBoxLayout(self)
self.main_layout.setContentsMargins(0, 0, 0, 0)
self.main_layout.setSpacing(0)
-
+
# 创建 Pivot 控件
self.pivot = SegmentedWidget(self)
-
+
# 创建堆叠窗口控件
self.stacked_widget = QStackedWidget(self)
-
+
# 添加到主布局
self.main_layout.addWidget(self.pivot, 0, Qt.AlignmentFlag.AlignHCenter)
self.main_layout.addWidget(self.stacked_widget)
-
+
# 连接信号
self.stacked_widget.currentChanged.connect(self.on_current_index_changed)
-
+
self.ui_created = True
-
+
# 添加页面
self.add_pages()
-
+
def add_pages(self):
"""根据配置添加所有页面"""
for page_name, display_name in self.page_config.items():
self.add_page(page_name, display_name)
-
+
# 如果有页面,设置第一个页面为当前页面
if self.pages:
first_page_name = next(iter(self.pages))
self.switch_to_page(first_page_name)
-
+
def add_page(self, page_name: str, display_name: str):
"""
添加单个页面
-
+
Args:
page_name: 页面名称,用于导入模块
display_name: 在 Pivot 中显示的名称
@@ -208,7 +239,7 @@ def add_page(self, page_name: str, display_name: str):
# 如果UI尚未创建,延迟添加
QTimer.singleShot(100, lambda: self.add_page(page_name, display_name))
return
-
+
# 创建滑动区域
scroll_area = SingleDirectionScrollArea(self)
scroll_area.setWidgetResizable(True)
@@ -222,36 +253,51 @@ def add_page(self, page_name: str, display_name: str):
background-color: transparent;
}
""")
- QScroller.grabGesture(scroll_area.viewport(), QScroller.ScrollerGestureType.LeftMouseButtonGesture)
+ QScroller.grabGesture(
+ scroll_area.viewport(), QScroller.ScrollerGestureType.LeftMouseButtonGesture
+ )
# 创建内部框架
inner_frame = QWidget(scroll_area)
inner_layout = QVBoxLayout(inner_frame)
- inner_layout.setAlignment(Qt.AlignmentFlag.AlignCenter | Qt.AlignmentFlag.AlignTop)
+ inner_layout.setAlignment(
+ Qt.AlignmentFlag.AlignCenter | Qt.AlignmentFlag.AlignTop
+ )
scroll_area.setWidget(inner_frame)
scroll_area.setObjectName(page_name)
-
+
# 添加到堆叠窗口
self.stacked_widget.addWidget(scroll_area)
-
+
# 添加到 Pivot
self.pivot.addItem(
routeKey=page_name,
text=display_name,
- onClick=lambda: self.switch_to_page(page_name)
+ onClick=lambda: self.switch_to_page(page_name),
)
-
+
# 存储滑动区域引用
self.pages[page_name] = scroll_area
-
+
# 延迟加载实际页面组件
- QTimer.singleShot(0, lambda: self._load_page_content(page_name, display_name, scroll_area, inner_layout))
-
- def _load_page_content(self, page_name: str, display_name: str, scroll_area: QScrollArea, inner_layout: QVBoxLayout):
+ QTimer.singleShot(
+ 0,
+ lambda: self._load_page_content(
+ page_name, display_name, scroll_area, inner_layout
+ ),
+ )
+
+ def _load_page_content(
+ self,
+ page_name: str,
+ display_name: str,
+ scroll_area: QScrollArea,
+ inner_layout: QVBoxLayout,
+ ):
"""
后台加载页面内容,避免堵塞进程
-
+
Args:
page_name: 页面名称
display_name: 在 Pivot 中显示的名称
@@ -260,79 +306,88 @@ def _load_page_content(self, page_name: str, display_name: str, scroll_area: QSc
"""
try:
# 动态导入页面组件
+ start = time.perf_counter()
module = importlib.import_module(f"{self.base_path}.{page_name}")
content_widget_class = getattr(module, page_name)
-
+
# 创建页面组件
widget = content_widget_class(self)
widget.setObjectName(page_name)
-
+
# 清除加载提示
- inner_layout.removeItem(inner_layout.itemAt(0))
-
+ if inner_layout.count() > 0:
+ item = inner_layout.itemAt(0)
+ if item:
+ inner_layout.removeItem(item)
+ if item.widget():
+ item.widget().deleteLater()
+
# 添加实际内容到内部布局
inner_layout.addWidget(widget)
-
+
+ elapsed = time.perf_counter() - start
+ logger.debug(f"加载页面组件 {page_name} 耗时: {elapsed:.3f}s")
+
# 如果当前页面就是正在加载的页面,确保滑动区域是当前可见的
if self.current_page == page_name:
self.stacked_widget.setCurrentWidget(scroll_area)
-
+
except (ImportError, AttributeError) as e:
print(f"无法导入页面组件 {page_name}: {e}")
-
+
# 清除加载提示
- inner_layout.removeItem(inner_layout.itemAt(0))
-
+ if inner_layout.count() > 0:
+ item = inner_layout.itemAt(0)
+ if item:
+ inner_layout.removeItem(item)
+ if item.widget():
+ item.widget().deleteLater()
+
# 创建错误页面
error_widget = QWidget()
error_layout = QVBoxLayout(error_widget)
error_layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
-
+
error_title = BodyLabel("页面加载失败")
error_title.setAlignment(Qt.AlignmentFlag.AlignCenter)
error_title.setFont(QFont(load_custom_font(), 16))
-
+
error_content = BodyLabel(f"无法加载页面 {page_name}: {str(e)}")
error_content.setAlignment(Qt.AlignmentFlag.AlignCenter)
-
+
error_layout.addWidget(error_title)
error_layout.addWidget(error_content)
error_layout.addStretch()
-
+
# 添加错误页面到内部布局
inner_layout.addWidget(error_widget)
-
+
# 如果当前页面就是正在加载的页面,确保滑动区域是当前可见的
if self.current_page == page_name:
self.stacked_widget.setCurrentWidget(scroll_area)
-
- # 删除占位符
- placeholder_widget = inner_layout.itemAt(0).widget()
- if placeholder_widget:
- placeholder_widget.deleteLater()
-
+
def switch_to_page(self, page_name: str):
"""切换到指定页面"""
if page_name in self.pages:
self.stacked_widget.setCurrentWidget(self.pages[page_name])
self.pivot.setCurrentItem(page_name)
self.current_page = page_name
-
+
def on_current_index_changed(self, index: int):
"""堆叠窗口索引改变时的处理"""
widget = self.stacked_widget.widget(index)
if widget:
self.pivot.setCurrentItem(widget.objectName())
self.current_page = widget.objectName()
-
+
def get_current_page(self) -> str:
"""获取当前页面名称"""
return self.current_page
-
+
def get_page(self, page_name: str):
"""根据页面名称获取页面组件"""
return self.pages.get(page_name, None)
-
+
def set_base_path(self, base_path: str):
"""设置页面模块的基础路径"""
- self.base_path = base_path
\ No newline at end of file
+ self.base_path = base_path
diff --git a/app/page_building/settings_window_page.py b/app/page_building/settings_window_page.py
index ca90a748..78ffd27c 100644
--- a/app/page_building/settings_window_page.py
+++ b/app/page_building/settings_window_page.py
@@ -1,111 +1,161 @@
# 导入库
-from PyQt6.QtWidgets import QFrame
+from PySide6.QtWidgets import QFrame
# 导入页面模板
from app.page_building.page_template import PageTemplate, PivotPageTemplate
# 导入自定义页面内容组件
-from app.view.settings.home import home
-from app.view.settings.basic_settings import basic_settings
-from app.view.settings.about import about
+# 为了延迟导入,传入字符串路径,实际类将在 PageTemplate.create_content 动态导入
+# content path format: 'module.submodule:ClassName' 或 'module.submodule.ClassName'
+HOME_PATH = "app.view.settings.home:home"
+BASIC_SETTINGS_PATH = "app.view.settings.basic_settings:basic_settings"
+ABOUT_PATH = "app.view.settings.about:about"
# 导入默认设置
from app.tools.settings_default import *
from app.Language.obtain_language import *
+
class home_page(PageTemplate):
"""创建主页页面"""
+
def __init__(self, parent: QFrame = None):
- super().__init__(content_widget_class=home, parent=parent)
+ super().__init__(content_widget_class=HOME_PATH, parent=parent)
+
class basic_settings_page(PageTemplate):
"""创建基础设置页面"""
+
def __init__(self, parent: QFrame = None):
- super().__init__(content_widget_class=basic_settings, parent=parent)
+ super().__init__(content_widget_class=BASIC_SETTINGS_PATH, parent=parent)
+
class list_management_page(PivotPageTemplate):
"""创建名单管理页面"""
+
def __init__(self, parent: QFrame = None):
page_config = {
"roll_call_list": get_content_name_async("roll_call_list", "title"),
"roll_call_table": get_content_name_async("roll_call_table", "title"),
"lottery_list": get_content_name_async("lottery_list", "title"),
- "lottery_table": get_content_name_async("lottery_table", "title")
+ "lottery_table": get_content_name_async("lottery_table", "title"),
}
super().__init__(page_config, parent)
self.set_base_path("app.view.settings.list_management")
+
class extraction_settings_page(PivotPageTemplate):
"""创建抽取设置页面"""
+
def __init__(self, parent: QFrame = None):
page_config = {
"roll_call_settings": get_content_name_async("roll_call_settings", "title"),
- "quick_draw_settings": get_content_name_async("quick_draw_settings", "title"),
- "instant_draw_settings": get_content_name_async("instant_draw_settings", "title"),
- "custom_draw_settings": get_content_name_async("custom_draw_settings", "title"),
- "lottery_settings": get_content_name_async("lottery_settings", "title")
+ "quick_draw_settings": get_content_name_async(
+ "quick_draw_settings", "title"
+ ),
+ "instant_draw_settings": get_content_name_async(
+ "instant_draw_settings", "title"
+ ),
+ "custom_draw_settings": get_content_name_async(
+ "custom_draw_settings", "title"
+ ),
+ "lottery_settings": get_content_name_async("lottery_settings", "title"),
}
super().__init__(page_config, parent)
self.set_base_path("app.view.settings.extraction_settings")
+
class notification_settings_page(PivotPageTemplate):
"""创建通知服务页面"""
+
def __init__(self, parent: QFrame = None):
page_config = {
- "roll_call_notification_settings": get_content_name_async("roll_call_notification_settings", "title"),
- "quick_draw_notification_settings": get_content_name_async("quick_draw_notification_settings", "title"),
- "instant_draw_notification_settings": get_content_name_async("instant_draw_notification_settings", "title"),
- "custom_draw_notification_settings": get_content_name_async("custom_draw_notification_settings", "title"),
- "lottery_notification_settings": get_content_name_async("lottery_notification_settings", "title"),
+ "roll_call_notification_settings": get_content_name_async(
+ "roll_call_notification_settings", "title"
+ ),
+ "quick_draw_notification_settings": get_content_name_async(
+ "quick_draw_notification_settings", "title"
+ ),
+ "instant_draw_notification_settings": get_content_name_async(
+ "instant_draw_notification_settings", "title"
+ ),
+ "custom_draw_notification_settings": get_content_name_async(
+ "custom_draw_notification_settings", "title"
+ ),
+ "lottery_notification_settings": get_content_name_async(
+ "lottery_notification_settings", "title"
+ ),
# "more_notification_settings": get_content_name_async("more_notification_settings", "title")
}
super().__init__(page_config, parent)
self.set_base_path("app.view.settings.notification_settings")
+
class safety_settings_page(PivotPageTemplate):
"""创建安全设置页面"""
+
def __init__(self, parent: QFrame = None):
page_config = {
- "basic_safety_settings": get_content_name_async("basic_safety_settings", "title"),
+ "basic_safety_settings": get_content_name_async(
+ "basic_safety_settings", "title"
+ ),
# "advanced_safety_settings": get_content_name_async("advanced_safety_settings", "title")
}
super().__init__(page_config, parent)
self.set_base_path("app.view.settings.safety_settings")
+
class custom_settings_page(PivotPageTemplate):
"""创建个性设置页面"""
+
def __init__(self, parent: QFrame = None):
page_config = {
"page_management": get_content_name_async("page_management", "title"),
- "floating_window_management": get_content_name_async("floating_window_management", "title"),
- "sidebar_tray_management": get_content_name_async("sidebar_tray_management", "title")
+ "floating_window_management": get_content_name_async(
+ "floating_window_management", "title"
+ ),
+ "sidebar_tray_management": get_content_name_async(
+ "sidebar_tray_management", "title"
+ ),
}
super().__init__(page_config, parent)
self.set_base_path("app.view.settings.custom_settings")
+
class voice_settings_page(PivotPageTemplate):
"""创建语音设置页面"""
+
def __init__(self, parent: QFrame = None):
page_config = {
- "basic_voice_settings": get_content_name_async("basic_voice_settings", "title"),
+ "basic_voice_settings": get_content_name_async(
+ "basic_voice_settings", "title"
+ ),
# "specific_announcements": get_content_name_async("specific_announcements", "title")
}
super().__init__(page_config, parent)
self.set_base_path("app.view.settings.voice_settings")
+
class history_page(PivotPageTemplate):
"""创建历史记录页面"""
+
def __init__(self, parent: QFrame = None):
page_config = {
"history_management": get_content_name_async("history_management", "title"),
- "roll_call_history_table": get_content_name_async("roll_call_history_table", "title"),
- "lottery_history_table": get_content_name_async("lottery_history_table", "title")
+ "roll_call_history_table": get_content_name_async(
+ "roll_call_history_table", "title"
+ ),
+ "lottery_history_table": get_content_name_async(
+ "lottery_history_table", "title"
+ ),
}
super().__init__(page_config, parent)
self.set_base_path("app.view.settings.history")
+
class more_settings_page(PivotPageTemplate):
"""创建更多设置页面"""
+
def __init__(self, parent: QFrame = None):
page_config = {
"advanced_settings": get_content_name_async("advanced_settings", "title"),
@@ -113,9 +163,11 @@ def __init__(self, parent: QFrame = None):
# "experimental_features": get_content_name_async("experimental_features", "title"),
}
super().__init__(page_config, parent)
- self.set_base_path("app.view.settings.more_settings")
+ self.set_base_path("app.view.settings.more_settings")
+
class about_page(PageTemplate):
"""创建关于页面"""
+
def __init__(self, parent: QFrame = None):
- super().__init__(content_widget_class=about, parent=parent)
\ No newline at end of file
+ super().__init__(content_widget_class=ABOUT_PATH, parent=parent)
diff --git a/app/page_building/window_template.py b/app/page_building/window_template.py
new file mode 100644
index 00000000..8f22c0df
--- /dev/null
+++ b/app/page_building/window_template.py
@@ -0,0 +1,429 @@
+# ==================================================
+# 导入库
+# ==================================================
+from typing import Dict, Optional, Type
+
+from loguru import logger
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from PySide6.QtNetwork import *
+from qfluentwidgets import *
+from qframelesswindow import *
+
+from app.tools.variable import *
+from app.tools.settings_access import *
+from app.tools.path_utils import *
+from app.tools.personalised import *
+from app.Language.obtain_language import *
+
+
+class SimpleWindowTemplate(FramelessWindow):
+ """简单窗口模板类
+
+ 提供一个更简单的窗口模板,不包含导航栏,适用于简单的对话框或弹出窗口。
+ 该类继承自FramelessWindow,提供了页面管理功能,可以动态添加、切换和移除页面。
+
+ 特性:
+ - 无边框窗口设计,带有标准标题栏
+ - 支持多页面管理,使用QStackedWidget实现页面切换
+ - 提供页面添加、移除和切换的便捷方法
+ - 支持从页面模板或已有控件创建页面
+ - 窗口关闭时发出windowClosed信号
+
+ 使用示例:
+ # 创建简单窗口
+ window = SimpleWindowTemplate("我的窗口", parent=None)
+
+ # 从页面模板添加页面
+ page_instance = window.add_page_from_template("settings", SettingsPage)
+
+ # 从已有控件添加页面
+ custom_widget = QWidget()
+ window.add_page_from_widget("custom", custom_widget)
+
+ # 切换到指定页面
+ window.switch_to_page("settings")
+
+ # 显示窗口
+ window.show()
+
+ 信号:
+ windowClosed: 窗口关闭时发出,无参数
+
+ 属性:
+ parent_window: 父窗口引用
+ pages: 页面类字典 {page_name: page_class}
+ page_instances: 页面实例字典 {page_name: page_instance}
+ stacked_widget: 页面堆叠窗口控件
+ main_layout: 主布局
+ """
+
+ # 信号定义
+ windowClosed = Signal()
+
+ def __init__(
+ self,
+ title: str = "窗口",
+ width: int = 700,
+ height: int = 500,
+ parent: Optional[QWidget] = None,
+ ):
+ """
+ 初始化简单窗口模板
+
+ Args:
+ title: 窗口标题
+ width: 窗口宽度
+ height: 窗口高度
+ parent: 父窗口
+ """
+ super().__init__(parent)
+
+ # 保存父窗口引用
+ self.parent_window = parent
+
+ # 存储页面
+ self.pages: Dict[str, Type] = {}
+ self.page_instances: Dict[str, QWidget] = {}
+
+ # 创建主布局
+ self.main_layout = QVBoxLayout(self)
+ self.main_layout.setContentsMargins(0, self.titleBar.height(), 0, 0)
+ self.main_layout.setSpacing(0)
+
+ # 创建堆叠窗口
+ self.stacked_widget = QStackedWidget(self)
+ self.main_layout.addWidget(self.stacked_widget)
+
+ # 连接信号
+ self.__connectSignalToSlot()
+
+ # 直接创建UI组件,不使用延迟初始化
+ self.create_ui_components()
+
+ # 初始化窗口
+ self.initWindow(title, width, height)
+
+ def initWindow(self, title: str, width: int = 700, height: int = 500) -> None:
+ """初始化窗口"""
+ self.setTitleBar(StandardTitleBar(self))
+ self.setMinimumSize(MINIMUM_WINDOW_SIZE[0], MINIMUM_WINDOW_SIZE[1])
+ self.resize(width, height)
+ self.setWindowIcon(
+ QIcon(str(get_resources_path("assets/icon", "secrandom-icon-paper.png")))
+ )
+ self.setWindowTitle(title)
+ self.titleBar.setAttribute(Qt.WidgetAttribute.WA_StyledBackground)
+ # 设置标题栏字体
+ custom_font = load_custom_font()
+ title_font = QFont(custom_font, 9)
+ self.titleBar.setFont(title_font)
+
+ # 确保在设置标题栏后应用当前主题和自定义字体
+ self._apply_current_theme()
+
+ if self.parent_window is None:
+ screen = QApplication.primaryScreen().availableGeometry()
+ w, h = screen.width(), screen.height()
+ self.move(w // 2 - self.width() // 2, h // 2 - self.height() // 2)
+
+ def __connectSignalToSlot(self) -> None:
+ """连接信号与槽"""
+ try:
+ qconfig.themeChanged.connect(self._on_theme_changed)
+ except Exception as e:
+ logger.error(f"连接信号时发生未知错误: {e}")
+
+ def _on_theme_changed(self) -> None:
+ """主题变化时自动更新窗口背景"""
+ try:
+ # 强制刷新窗口背景
+ self._apply_current_theme()
+ except Exception as e:
+ logger.error(f"主题变化时更新窗口背景失败: {e}")
+
+ def _apply_current_theme(self) -> None:
+ """应用当前主题设置到窗口"""
+ try:
+ # 获取当前主题设置
+ current_theme = readme_settings("basic_settings", "theme")
+
+ # 根据主题设置窗口背景色
+ if current_theme == "DARK":
+ # 深色主题 - 设置深色背景
+ self.setStyleSheet("background-color: #202020;")
+ self.default_page.setStyleSheet("background-color: transparent;")
+ elif current_theme == "AUTO":
+ # 自动主题 - 根据系统设置
+ try:
+ from darkdetect import isDark
+ if isDark():
+ self.setStyleSheet("background-color: #202020;")
+ self.default_page.setStyleSheet("background-color: transparent;")
+ else:
+ self.setStyleSheet("background-color: #ffffff;")
+ self.default_page.setStyleSheet("background-color: transparent;")
+ except:
+ # 如果检测失败,使用浅色主题
+ self.setStyleSheet("background-color: #ffffff;")
+ self.default_page.setStyleSheet("background-color: transparent;")
+ else:
+ # 浅色主题
+ self.setStyleSheet("background-color: #ffffff;")
+ self.default_page.setStyleSheet("background-color: transparent;")
+
+ # 应用标题栏自定义字体和颜色
+ self._set_titlebar_colors()
+
+ logger.debug(f"窗口主题已更新为: {current_theme}")
+ except Exception as e:
+ logger.error(f"应用主题时出错: {e}")
+ # 设置默认的浅色背景作为备选
+ self.setStyleSheet("background-color: #ffffff;")
+ self.default_page.setStyleSheet("background-color: transparent;")
+
+ def _set_titlebar_colors(self) -> None:
+ """设置标题栏颜色"""
+ try:
+ # 判断是否为深色主题
+ is_dark = is_dark_theme(qconfig)
+
+ if is_dark:
+ # 深色主题
+ title_color = "#ffffff" # 白色文字
+ background_color = "#202020"
+ else:
+ # 浅色主题
+ title_color = "#000000" # 黑色文字
+ background_color = "#ffffff"
+
+ # 设置标题栏颜色样式
+ titlebar_style = f"""
+ QWidget {{
+ color: {title_color};
+ background-color: {background_color};
+ }}
+ QLabel {{
+ color: {title_color};
+ background-color: transparent;
+ }}
+ QPushButton {{
+ color: {title_color};
+ background-color: transparent;
+ }}
+ QToolButton {{
+ color: {title_color};
+ background-color: transparent;
+ }}
+ #minimizeButton, #maximizeButton, #closeButton {{
+ color: {title_color};
+ background-color: transparent;
+ }}
+ #minimizeButton:hover, #maximizeButton:hover, #closeButton:hover {{
+ background-color: rgba(255, 255, 255, 0.1);
+ }}
+ #closeButton:hover {{
+ background-color: rgba(232, 17, 35, 0.8);
+ }}
+ """
+ self.titleBar.setStyleSheet(titlebar_style)
+
+ logger.debug(f"标题栏颜色已设置: 文字色={title_color}, 背景色={background_color}")
+ except Exception as e:
+ logger.error(f"设置标题栏颜色失败: {e}")
+
+ def create_ui_components(self) -> None:
+ """创建UI组件"""
+ try:
+ # 创建默认页面
+ self.default_page = QWidget()
+ default_layout = QVBoxLayout(self.default_page)
+ default_layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
+
+ # 添加默认页面到堆叠窗口
+ self.stacked_widget.addWidget(self.default_page)
+ except Exception as e:
+ logger.error(f"创建UI组件时出错: {e}")
+ raise
+
+ def add_page_from_template(
+ self, page_name: str, page_class: Type
+ ) -> Optional[QWidget]:
+ """
+ 从页面模板添加页面
+
+ Args:
+ page_name: 页面名称(唯一标识)
+ page_class: 页面类
+ width: 页面宽度
+ height: 页面高度
+
+ Returns:
+ 页面实例,如果创建失败则返回None
+ """
+ # 输入验证
+ if not page_name or not isinstance(page_name, str):
+ logger.error("页面名称必须是非空字符串")
+ return None
+
+ if page_name in self.page_instances:
+ logger.warning(f"页面 {page_name} 已存在,将返回现有实例")
+ return self.page_instances[page_name]
+
+ try:
+ # 创建页面实例
+ page_instance = page_class(self)
+
+ # 设置对象名称
+ page_instance.setObjectName(page_name)
+
+ # 添加到堆叠窗口
+ self.stacked_widget.addWidget(page_instance)
+
+ # 存储页面
+ self.page_instances[page_name] = page_instance
+ self.pages[page_name] = page_class
+ return page_instance
+ except Exception as e:
+ logger.error(f"创建页面 {page_name} 时出错: {e}")
+ return None
+
+ def add_page_from_widget(self, page_name: str, widget: QWidget) -> QWidget:
+ """
+ 从控件添加页面
+
+ Args:
+ page_name: 页面名称(唯一标识)
+ widget: 页面控件
+
+ Returns:
+ 页面控件
+
+ Raises:
+ ValueError: 如果输入参数无效
+ """
+ # 输入验证
+ if not page_name or not isinstance(page_name, str):
+ raise ValueError("页面名称必须是非空字符串")
+
+ if page_name in self.page_instances:
+ logger.warning(f"页面 {page_name} 已存在,将返回现有实例")
+ return self.page_instances[page_name]
+
+ # 设置对象名称
+ widget.setObjectName(page_name)
+
+ try:
+ # 添加到堆叠窗口
+ self.stacked_widget.addWidget(widget)
+
+ # 存储页面
+ self.page_instances[page_name] = widget
+ return widget
+ except Exception as e:
+ logger.error(f"添加页面 {page_name} 到堆叠窗口时出错: {e}")
+ raise
+
+ def get_page(self, page_name: str) -> Optional[QWidget]:
+ """
+ 获取页面实例
+
+ Args:
+ page_name: 页面名称
+
+ Returns:
+ 页面实例,如果不存在则返回None
+ """
+ # 输入验证
+ if not page_name or not isinstance(page_name, str):
+ logger.warning("页面名称必须是非空字符串")
+ return None
+
+ page = self.page_instances.get(page_name, None)
+ if page is None:
+ logger.debug(f"请求的页面 {page_name} 不存在")
+
+ return page
+
+ def remove_page(self, page_name: str) -> bool:
+ """
+ 移除页面
+
+ Args:
+ page_name: 页面名称
+
+ Returns:
+ 是否成功移除页面
+ """
+ # 输入验证
+ if not page_name or not isinstance(page_name, str):
+ logger.error("页面名称必须是非空字符串")
+ return False
+
+ if page_name not in self.page_instances:
+ logger.warning(f"尝试移除不存在的页面: {page_name}")
+ return False
+
+ try:
+ page = self.page_instances[page_name]
+
+ # 如果当前显示的是要删除的页面,先切换到默认页面
+ if self.stacked_widget.currentWidget() == page:
+ if self.default_page:
+ self.stacked_widget.setCurrentWidget(self.default_page)
+ else:
+ logger.warning(
+ f"删除当前显示的页面 {page_name} 但没有默认页面可切换"
+ )
+
+ # 从堆叠窗口中移除
+ self.stacked_widget.removeWidget(page)
+
+ # 从存储中移除
+ del self.page_instances[page_name]
+ if page_name in self.pages:
+ del self.pages[page_name]
+ return True
+ except Exception as e:
+ logger.error(f"移除页面 {page_name} 时出错: {e}")
+ return False
+
+ def switch_to_page(self, page_name: str) -> bool:
+ """
+ 切换到指定页面
+
+ Args:
+ page_name: 页面名称
+
+ Returns:
+ 是否成功切换到页面
+ """
+ # 输入验证
+ if not page_name or not isinstance(page_name, str):
+ logger.error("页面名称必须是非空字符串")
+ return False
+
+ if page_name not in self.page_instances:
+ logger.warning(f"尝试切换到不存在的页面: {page_name}")
+ return False
+
+ try:
+ target_page = self.page_instances[page_name]
+ current_page = self.stacked_widget.currentWidget()
+
+ # 检查是否已经在目标页面
+ if current_page == target_page:
+ return True
+
+ # 切换页面
+ self.stacked_widget.setCurrentWidget(target_page)
+ return True
+ except Exception as e:
+ logger.error(f"切换到页面 {page_name} 时出错: {e}")
+ return False
+
+ def closeEvent(self, event) -> None:
+ """窗口关闭事件处理"""
+ self.windowClosed.emit()
+ super().closeEvent(event)
diff --git a/app/resources/Language/EN_US.json b/app/resources/Language/EN_US.json
new file mode 100644
index 00000000..aede3317
--- /dev/null
+++ b/app/resources/Language/EN_US.json
@@ -0,0 +1,1015 @@
+{
+ "translate_JSON_file": {
+ "name": "English (United States)",
+ "entertainment": false,
+ "translated_personnel": "MacrosMeng"
+ },
+ "home": {
+ "title": {
+ "name": "Homepage",
+ "description": "The homepage of this software"
+ }
+ },
+ "basic_settings": {
+ "title": {
+ "name": "General Settings",
+ "description": "The general settings of this app"
+ },
+ "basic_function": {
+ "name": "Basic Features",
+ "description": "Features of this app"
+ },
+ "data_management": {
+ "name": "Data Management",
+ "description": "Manage your data"
+ },
+ "personalised": {
+ "name": "Customize",
+ "description": "Customize your app"
+ },
+ "autostart": {
+ "name": "Autostart on Boot",
+ "description": "Set whether the app starts automatically on system boot",
+ "switchbutton_name": {
+ "enable": "Enable",
+ "disable": "Disable"
+ }
+ },
+ "check_update": {
+ "name": "Check for Updates",
+ "description": "Set whether to check for updates at startup",
+ "switchbutton_name": {
+ "enable": "Enable",
+ "disable": "Disable"
+ }
+ },
+ "show_startup_window": {
+ "name": "Show Startup Window",
+ "description": "Set whether to show the startup window at launch",
+ "switchbutton_name": {
+ "enable": "Show",
+ "disable": "Don't show"
+ }
+ },
+ "export_diagnostic_data": {
+ "name": "Export Diagnostic Data",
+ "description": "Set whether to export diagnostic data on exit",
+ "pushbutton_name": "Export Diagnostic Data"
+ },
+ "export_settings": {
+ "name": "Export Settings",
+ "description": "Set whether to export settings on exit",
+ "pushbutton_name": "Export Settings"
+ },
+ "import_settings": {
+ "name": "Import Settings",
+ "description": "Set whether to import settings on startup",
+ "pushbutton_name": "Import Settings"
+ },
+ "export_all_data": {
+ "name": "Export All Data",
+ "description": "Set whether to export all data on exit",
+ "pushbutton_name": "Export All Data"
+ },
+ "import_all_data": {
+ "name": "Import All Data",
+ "description": "Set whether to import all data on startup",
+ "pushbutton_name": "Import All Data"
+ },
+ "dpiScale": {
+ "name": "DPI Scale",
+ "description": "Set the app DPI scaling (requires restart)",
+ "combo_items": [
+ "100%",
+ "125%",
+ "150%",
+ "175%",
+ "200%",
+ "Auto"
+ ]
+ },
+ "font": {
+ "name": "Font",
+ "description": "Set the app font (requires restart)"
+ },
+ "theme": {
+ "name": "Theme",
+ "description": "Set the app theme",
+ "combo_items": [
+ "Light",
+ "Dark",
+ "Follow system settings"
+ ]
+ },
+ "theme_color": {
+ "name": "Theme Color",
+ "description": "App theme color"
+ },
+ "language": {
+ "name": "Language",
+ "description": "Set the app language (requires restart)"
+ }
+ },
+ "list_management": {
+ "title": {
+ "name": "List Management",
+ "description": "The app's list management page"
+ }
+ },
+ "roll_call_list": {
+ "title": {
+ "name": "Roll Call Lists",
+ "description": "Lists for roll call"
+ },
+ "set_class_name": {
+ "name": "Set Class Name",
+ "description": "Set the class name"
+ },
+ "select_class_name": {
+ "name": "Select Class",
+ "description": "Select a class"
+ },
+ "import_student_name": {
+ "name": "Import Student List",
+ "description": "Import a list of students"
+ },
+ "name_setting": {
+ "name": "Name Settings",
+ "description": "Configure names"
+ },
+ "gender_setting": {
+ "name": "Gender Settings",
+ "description": "Configure gender"
+ },
+ "group_setting": {
+ "name": "Group Settings",
+ "description": "Configure groups"
+ },
+ "export_student_name": {
+ "name": "Export Student List",
+ "description": "Export the student list"
+ }
+ },
+ "roll_call_table": {
+ "title": {
+ "name": "Roll Call Table",
+ "description": "Table for displaying and managing roll call lists"
+ },
+ "HeaderLabels": {
+ "name": [
+ "Present",
+ "Student ID",
+ "Name",
+ "Gender",
+ "Group"
+ ],
+ "description": "Column headers for the roll call table"
+ }
+ },
+ "custom_draw_list": {
+ "title": {
+ "name": "Custom Draw",
+ "description": "Custom drawing (lottery) lists"
+ }
+ },
+ "lottery_list": {
+ "title": {
+ "name": "Lottery Lists",
+ "description": "Lists for lotteries/prize draws"
+ },
+ "set_pool_name": {
+ "name": "Set Prize Pool Name",
+ "description": "Set the name of the prize pool"
+ },
+ "select_pool_name": {
+ "name": "Select Prize Pool",
+ "description": "Select a prize pool"
+ },
+ "import_prize_name": {
+ "name": "Import Prize List",
+ "description": "Import a list of prizes"
+ },
+ "prize_setting": {
+ "name": "Prize Settings",
+ "description": "Configure prizes"
+ },
+ "prize_weight_setting": {
+ "name": "Set Weight",
+ "description": "Configure prize weight"
+ },
+ "export_prize_name": {
+ "name": "Export Prize List",
+ "description": "Export the prize list"
+ }
+ },
+ "lottery_table": {
+ "title": {
+ "name": "Lottery Table",
+ "description": "Table for displaying and managing lottery lists"
+ },
+ "HeaderLabels": {
+ "name": [
+ "Exists",
+ "Index",
+ "Prize",
+ "Weight"
+ ],
+ "description": "Column headers for the lottery table"
+ }
+ },
+ "extraction_settings": {
+ "title": {
+ "name": "Extraction Settings",
+ "description": "Settings related to extraction/drawing"
+ }
+ },
+ "roll_call_settings": {
+ "title": {
+ "name": "Roll Call Settings",
+ "description": "Settings for roll call"
+ },
+ "extraction_function": {
+ "name": "Extraction Functions",
+ "description": "Settings related to roll call extraction features"
+ },
+ "display_settings": {
+ "name": "Display Settings",
+ "description": "Settings for how roll call results are displayed"
+ },
+ "basic_animation_settings": {
+ "name": "Animation Settings",
+ "description": "Settings related to roll call animation effects"
+ },
+ "color_theme_settings": {
+ "name": "Color Theme Settings",
+ "description": "Color theme settings for roll call results display"
+ },
+ "student_image_settings": {
+ "name": "Student Avatar Settings",
+ "description": "Settings for student avatars in roll call results display"
+ },
+ "music_settings": {
+ "name": "Music Settings",
+ "description": "Settings related to roll call music"
+ },
+ "draw_mode": {
+ "name": "Draw Mode",
+ "description": "Set the draw mode for roll call",
+ "combo_items": [
+ "Allow Repeats",
+ "No Repeats",
+ "Partial Repeats"
+ ]
+ },
+ "clear_record": {
+ "name": "Clear Record Mode",
+ "description": "Set how roll call extraction records are cleared",
+ "combo_items": [
+ "Clear After Restart",
+ "Until All Selected"
+ ],
+ "combo_items_other": [
+ "Clear After Restart",
+ "Until All Selected",
+ "Never Clear"
+ ]
+ },
+ "half_repeat": {
+ "name": "Partial Repeat Count",
+ "description": "Set the number of partial repeats"
+ },
+ "clear_time": {
+ "name": "Timed Clear After Extraction",
+ "description": "Set the time (seconds) after which extraction records are cleared"
+ },
+ "draw_type": {
+ "name": "Draw Type",
+ "description": "Set the draw type for roll call",
+ "combo_items": [
+ "Random Draw",
+ "Fair Draw"
+ ]
+ },
+ "font_size": {
+ "name": "Font Size",
+ "description": "Set the font size for roll call results display"
+ },
+ "display_format": {
+ "name": "Result Display Format",
+ "description": "Set the format for displaying roll call results",
+ "combo_items": [
+ "Student ID + Name",
+ "Name",
+ "Student ID"
+ ]
+ },
+ "show_random": {
+ "name": "Show Random Group Member Format",
+ "description": "Set how random group members are shown",
+ "combo_items": [
+ "Don't show",
+ "GroupName[Newline]Name",
+ "GroupName[-]Name"
+ ]
+ },
+ "animation": {
+ "name": "Animation Mode",
+ "description": "Set the animation effect for roll call draws",
+ "combo_items": [
+ "Manual Stop Animation",
+ "Auto Play Animation",
+ "Show Result Immediately"
+ ]
+ },
+ "animation_interval": {
+ "name": "Animation Interval",
+ "description": "Set the interval for roll call animation (ms)"
+ },
+ "autoplay_count": {
+ "name": "Autoplay Count",
+ "description": "Set how many times the roll call animation auto-plays"
+ },
+ "animation_color_theme": {
+ "name": "Animation Color Theme",
+ "description": "Set the color theme for roll call animation",
+ "combo_items": [
+ "Off",
+ "Random Colors",
+ "Fixed Color"
+ ]
+ },
+ "result_color_theme": {
+ "name": "Result Color Theme",
+ "description": "Set the color theme for roll call result display",
+ "combo_items": [
+ "Off",
+ "Random Colors",
+ "Fixed Color"
+ ]
+ },
+ "animation_fixed_color": {
+ "name": "Animation Fixed Color",
+ "description": "Set the fixed color used during roll call animations"
+ },
+ "result_fixed_color": {
+ "name": "Result Fixed Color",
+ "description": "Set the fixed color used for roll call results display"
+ },
+ "student_image": {
+ "name": "Show Student Images",
+ "description": "Set whether to show student images",
+ "switchbutton_name": {
+ "enable": "Show",
+ "disable": "Hide"
+ }
+ },
+ "open_student_image_folder": {
+ "name": "Student Image Folder",
+ "description": "Manage student image files; image filenames must match student names"
+ },
+ "animation_music": {
+ "name": "Animation Music",
+ "description": "Set whether to play animation music",
+ "switchbutton_name": {
+ "enable": "Play",
+ "disable": "Off"
+ }
+ },
+ "result_music": {
+ "name": "Result Music",
+ "description": "Set whether to play result music",
+ "switchbutton_name": {
+ "enable": "Play",
+ "disable": "Off"
+ }
+ },
+ "open_animation_music_folder": {
+ "name": "Open Animation Music Folder",
+ "description": "Manage animation music files and enable random playback"
+ },
+ "open_result_music_folder": {
+ "name": "Open Result Music Folder",
+ "description": "Manage result music files and enable random playback"
+ },
+ "animation_music_volume": {
+ "name": "Animation Music Volume",
+ "description": "Set the volume for animation music"
+ },
+ "result_music_volume": {
+ "name": "Result Music Volume",
+ "description": "Set the volume for result music"
+ },
+ "animation_music_fade_in": {
+ "name": "Animation Music Fade-In Time",
+ "description": "Set the fade-in time for animation music"
+ },
+ "result_music_fade_in": {
+ "name": "Result Music Fade-In Time",
+ "description": "Set the fade-in time for result music"
+ },
+ "animation_music_fade_out": {
+ "name": "Animation Music Fade-Out Time",
+ "description": "Set the fade-out time for animation music"
+ },
+ "result_music_fade_out": {
+ "name": "Result Music Fade-Out Time",
+ "description": "Set the fade-out time for result music"
+ }
+ },
+ "quick_draw_settings": {
+ "title": {
+ "name": "Quick Draw Settings",
+ "description": "Settings for quick draws"
+ },
+ "extraction_function": {
+ "name": "Extraction Functions",
+ "description": "Settings related to quick draw extraction features"
+ },
+ "display_settings": {
+ "name": "Display Settings",
+ "description": "Settings for how quick draw results are displayed"
+ },
+ "basic_animation_settings": {
+ "name": "Animation Settings",
+ "description": "Settings related to quick draw animation effects"
+ },
+ "color_theme_settings": {
+ "name": "Color Theme Settings",
+ "description": "Color theme settings for quick draw results display"
+ },
+ "student_image_settings": {
+ "name": "Student Avatar Settings",
+ "description": "Settings for student avatars in quick draw results display"
+ },
+ "music_settings": {
+ "name": "Music Settings",
+ "description": "Settings related to quick draw music"
+ },
+ "draw_mode": {
+ "name": "Draw Mode",
+ "description": "Set the draw mode for quick draw",
+ "combo_items": [
+ "Allow Repeats",
+ "No Repeats",
+ "Partial Repeats"
+ ]
+ },
+ "clear_record": {
+ "name": "Clear Record Mode",
+ "description": "Set how quick draw extraction records are cleared",
+ "combo_items": [
+ "Clear After Restart",
+ "Until All Selected"
+ ],
+ "combo_items_other": [
+ "Clear After Restart",
+ "Until All Selected",
+ "Never Clear"
+ ]
+ },
+ "half_repeat": {
+ "name": "Partial Repeat Count",
+ "description": "Set the number of partial repeats"
+ },
+ "clear_time": {
+ "name": "Timed Clear After Extraction",
+ "description": "Set the time (seconds) after which extraction records are cleared"
+ },
+ "draw_type": {
+ "name": "Draw Type",
+ "description": "Set the draw type for quick draw",
+ "combo_items": [
+ "Random Draw",
+ "Fair Draw"
+ ]
+ },
+ "font_size": {
+ "name": "Font Size",
+ "description": "Set the font size for quick draw results display"
+ },
+ "display_format": {
+ "name": "Result Display Format",
+ "description": "Set the format for displaying quick draw results",
+ "combo_items": [
+ "Student ID + Name",
+ "Name",
+ "Student ID"
+ ]
+ },
+ "show_random": {
+ "name": "Show Random Group Member Format",
+ "description": "Set how random group members are shown",
+ "combo_items": [
+ "Don't show",
+ "GroupName[Newline]Name",
+ "GroupName[-]Name"
+ ]
+ },
+ "animation": {
+ "name": "Animation Mode",
+ "description": "Set the animation effect for quick draw",
+ "combo_items": [
+ "Manual Stop Animation",
+ "Auto Play Animation",
+ "Show Result Immediately"
+ ]
+ },
+ "animation_interval": {
+ "name": "Animation Interval",
+ "description": "Set the interval for quick draw animation (ms)"
+ },
+ "autoplay_count": {
+ "name": "Autoplay Count",
+ "description": "Set how many times the quick draw animation auto-plays"
+ },
+ "animation_color_theme": {
+ "name": "Animation Color Theme",
+ "description": "Set the color theme for quick draw animation",
+ "combo_items": [
+ "Off",
+ "Random Colors",
+ "Fixed Color"
+ ]
+ },
+ "result_color_theme": {
+ "name": "Result Color Theme",
+ "description": "Set the color theme for quick draw result display",
+ "combo_items": [
+ "Off",
+ "Random Colors",
+ "Fixed Color"
+ ]
+ },
+ "animation_fixed_color": {
+ "name": "Animation Fixed Color",
+ "description": "Set the fixed color used during quick draw animations"
+ },
+ "result_fixed_color": {
+ "name": "Result Fixed Color",
+ "description": "Set the fixed color used for quick draw results display"
+ },
+ "student_image": {
+ "name": "Show Student Images",
+ "description": "Set whether to show student images",
+ "switchbutton_name": {
+ "enable": "Show",
+ "disable": "Hide"
+ }
+ },
+ "open_student_image_folder": {
+ "name": "Student Image Folder",
+ "description": "Manage student image files; image filenames must match student names"
+ },
+ "animation_music": {
+ "name": "Animation Music",
+ "description": "Set whether to play animation music",
+ "switchbutton_name": {
+ "enable": "Play",
+ "disable": "Off"
+ }
+ },
+ "result_music": {
+ "name": "Result Music",
+ "description": "Set whether to play result music",
+ "switchbutton_name": {
+ "enable": "Play",
+ "disable": "Off"
+ }
+ },
+ "open_animation_music_folder": {
+ "name": "Open Animation Music Folder",
+ "description": "Manage animation music files and enable random playback"
+ },
+ "open_result_music_folder": {
+ "name": "Open Result Music Folder",
+ "description": "Manage result music files and enable random playback"
+ },
+ "animation_music_volume": {
+ "name": "Animation Music Volume",
+ "description": "Set the volume for animation music"
+ },
+ "result_music_volume": {
+ "name": "Result Music Volume",
+ "description": "Set the volume for result music"
+ },
+ "animation_music_fade_in": {
+ "name": "Animation Music Fade-In Time",
+ "description": "Set the fade-in time for animation music"
+ },
+ "result_music_fade_in": {
+ "name": "Result Music Fade-In Time",
+ "description": "Set the fade-in time for result music"
+ },
+ "animation_music_fade_out": {
+ "name": "Animation Music Fade-Out Time",
+ "description": "Set the fade-out time for animation music"
+ },
+ "result_music_fade_out": {
+ "name": "Result Music Fade-Out Time",
+ "description": "Set the fade-out time for result music"
+ }
+ },
+ "instant_draw_settings": {
+ "title": {
+ "name": "Instant Draw Settings",
+ "description": "Settings related to instant draw functionality"
+ },
+ "extraction_function": {
+ "name": "Extraction Functions",
+ "description": "Settings related to instant draw extraction features"
+ },
+ "display_settings": {
+ "name": "Display Settings",
+ "description": "Settings for how instant draw results are displayed"
+ },
+ "basic_animation_settings": {
+ "name": "Animation Settings",
+ "description": "Settings related to instant draw animation effects"
+ },
+ "color_theme_settings": {
+ "name": "Color Theme Settings",
+ "description": "Color theme settings for instant draw results display"
+ },
+ "student_image_settings": {
+ "name": "Student Avatar Settings",
+ "description": "Settings for student avatars in instant draw results display"
+ },
+ "music_settings": {
+ "name": "Music Settings",
+ "description": "Settings related to instant draw music"
+ },
+ "draw_mode": {
+ "name": "Draw Mode",
+ "description": "Set the draw mode for instant draw",
+ "combo_items": [
+ "Allow Repeats",
+ "No Repeats",
+ "Partial Repeats"
+ ]
+ },
+ "clear_record": {
+ "name": "Clear Record Mode",
+ "description": "Set how instant draw extraction records are cleared",
+ "combo_items": [
+ "Clear After Restart",
+ "Until All Selected"
+ ],
+ "combo_items_other": [
+ "Clear After Restart",
+ "Until All Selected",
+ "Never Clear"
+ ]
+ },
+ "half_repeat": {
+ "name": "Partial Repeat Count",
+ "description": "Set the number of partial repeats"
+ },
+ "clear_time": {
+ "name": "Timed Clear After Extraction",
+ "description": "Set the time (seconds) after which extraction records are cleared"
+ },
+ "draw_type": {
+ "name": "Draw Type",
+ "description": "Set the draw type for instant draw",
+ "combo_items": [
+ "Random Draw",
+ "Fair Draw"
+ ]
+ },
+ "font_size": {
+ "name": "Font Size",
+ "description": "Set the font size for instant draw results display"
+ },
+ "display_format": {
+ "name": "Result Display Format",
+ "description": "Set the format for displaying instant draw results",
+ "combo_items": [
+ "Student ID + Name",
+ "Name",
+ "Student ID"
+ ]
+ },
+ "show_random": {
+ "name": "Show Random Group Member Format",
+ "description": "Set how random group members are shown",
+ "combo_items": [
+ "Don't show",
+ "GroupName[Newline]Name",
+ "GroupName[-]Name"
+ ]
+ },
+ "animation": {
+ "name": "Animation Mode",
+ "description": "Set the animation effect for instant draw",
+ "combo_items": [
+ "Manual Stop Animation",
+ "Auto Play Animation",
+ "Show Result Immediately"
+ ]
+ },
+ "animation_interval": {
+ "name": "Animation Interval",
+ "description": "Set the interval for instant draw animation (ms)"
+ },
+ "autoplay_count": {
+ "name": "Autoplay Count",
+ "description": "Set how many times the instant draw animation auto-plays"
+ },
+ "animation_color_theme": {
+ "name": "Animation Color Theme",
+ "description": "Set the color theme for instant draw animation",
+ "combo_items": [
+ "Off",
+ "Random Colors",
+ "Fixed Color"
+ ]
+ },
+ "result_color_theme": {
+ "name": "Result Color Theme",
+ "description": "Set the color theme for instant draw result display",
+ "combo_items": [
+ "Off",
+ "Random Colors",
+ "Fixed Color"
+ ]
+ },
+ "animation_fixed_color": {
+ "name": "Animation Fixed Color",
+ "description": "Set the fixed color used during instant draw animations"
+ },
+ "result_fixed_color": {
+ "name": "Result Fixed Color",
+ "description": "Set the fixed color used for instant draw results display"
+ },
+ "student_image": {
+ "name": "Show Student Images",
+ "description": "Set whether to show student images",
+ "switchbutton_name": {
+ "enable": "Show",
+ "disable": "Hide"
+ }
+ },
+ "open_student_image_folder": {
+ "name": "Student Image Folder",
+ "description": "Manage student image files; image filenames must match student names"
+ },
+ "animation_music": {
+ "name": "Animation Music",
+ "description": "Set whether to play animation music",
+ "switchbutton_name": {
+ "enable": "Play",
+ "disable": "Off"
+ }
+ },
+ "result_music": {
+ "name": "Result Music",
+ "description": "Set whether to play result music",
+ "switchbutton_name": {
+ "enable": "Play",
+ "disable": "Off"
+ }
+ },
+ "open_animation_music_folder": {
+ "name": "Open Animation Music Folder",
+ "description": "Manage animation music files and enable random playback"
+ },
+ "open_result_music_folder": {
+ "name": "Open Result Music Folder",
+ "description": "Manage result music files and enable random playback"
+ },
+ "animation_music_volume": {
+ "name": "Animation Music Volume",
+ "description": "Set the volume for animation music"
+ },
+ "result_music_volume": {
+ "name": "Result Music Volume",
+ "description": "Set the volume for result music"
+ },
+ "animation_music_fade_in": {
+ "name": "Animation Music Fade-In Time",
+ "description": "Set the fade-in time for animation music"
+ },
+ "result_music_fade_in": {
+ "name": "Result Music Fade-In Time",
+ "description": "Set the fade-in time for result music"
+ },
+ "animation_music_fade_out": {
+ "name": "Animation Music Fade-Out Time",
+ "description": "Set the fade-out time for animation music"
+ },
+ "result_music_fade_out": {
+ "name": "Result Music Fade-Out Time",
+ "description": "Set the fade-out time for result music"
+ }
+ },
+ "custom_draw_settings": {
+ "title": {
+ "name": "Custom Draw Settings",
+ "description": "Settings for custom draws"
+ }
+ },
+ "lottery_settings": {
+ "title": {
+ "name": "Lottery Settings",
+ "description": "Settings for lottery/prize draws"
+ },
+ "extraction_function": {
+ "name": "Extraction Functions",
+ "description": "Settings related to lottery extraction features"
+ },
+ "display_settings": {
+ "name": "Display Settings",
+ "description": "Settings for how lottery results are displayed"
+ },
+ "basic_animation_settings": {
+ "name": "Animation Settings",
+ "description": "Settings related to lottery animation effects"
+ },
+ "color_theme_settings": {
+ "name": "Color Theme Settings",
+ "description": "Color theme settings for lottery results display"
+ },
+ "student_image_settings": {
+ "name": "Prize Image Settings",
+ "description": "Settings for prize images in lottery results display"
+ },
+ "music_settings": {
+ "name": "Music Settings",
+ "description": "Settings related to lottery music"
+ },
+ "draw_mode": {
+ "name": "Draw Mode",
+ "description": "Set the draw mode for lotteries",
+ "combo_items": [
+ "Allow Repeats",
+ "No Repeats",
+ "Partial Repeats"
+ ]
+ },
+ "clear_record": {
+ "name": "Clear Record Mode",
+ "description": "Set how lottery extraction records are cleared",
+ "combo_items": [
+ "Clear After Restart",
+ "Until All Selected"
+ ],
+ "combo_items_other": [
+ "Clear After Restart",
+ "Until All Selected",
+ "Never Clear"
+ ]
+ },
+ "half_repeat": {
+ "name": "Partial Repeat Count",
+ "description": "Set the number of partial repeats"
+ },
+ "clear_time": {
+ "name": "Timed Clear After Extraction",
+ "description": "Set the time (seconds) after which extraction records are cleared"
+ },
+ "draw_type": {
+ "name": "Draw Type",
+ "description": "Set the draw type for lotteries",
+ "combo_items": [
+ "Random Draw",
+ "Fair Draw"
+ ]
+ },
+ "font_size": {
+ "name": "Font Size",
+ "description": "Set the font size for lottery results display"
+ },
+ "display_format": {
+ "name": "Result Display Format",
+ "description": "Set the format for displaying lottery results",
+ "combo_items": [
+ "Index + Name",
+ "Name",
+ "Index"
+ ]
+ },
+ "animation": {
+ "name": "Animation Mode",
+ "description": "Set the animation effect for lottery draws",
+ "combo_items": [
+ "Manual Stop Animation",
+ "Auto Play Animation",
+ "Show Result Immediately"
+ ]
+ },
+ "animation_interval": {
+ "name": "Animation Interval",
+ "description": "Set the interval for lottery animation (ms)"
+ },
+ "autoplay_count": {
+ "name": "Autoplay Count",
+ "description": "Set how many times the lottery animation auto-plays"
+ },
+ "animation_color_theme": {
+ "name": "Animation Color Theme",
+ "description": "Set the color theme for lottery animation",
+ "combo_items": [
+ "Off",
+ "Random Colors",
+ "Fixed Color"
+ ]
+ },
+ "result_color_theme": {
+ "name": "Result Color Theme",
+ "description": "Set the color theme for lottery result display",
+ "combo_items": [
+ "Off",
+ "Random Colors",
+ "Fixed Color"
+ ]
+ },
+ "animation_fixed_color": {
+ "name": "Animation Fixed Color",
+ "description": "Set the fixed color used during lottery animations"
+ },
+ "result_fixed_color": {
+ "name": "Result Fixed Color",
+ "description": "Set the fixed color used for lottery results display"
+ },
+ "lottery_image": {
+ "name": "Show Item Images",
+ "description": "Set whether to show images for items/prizes",
+ "switchbutton_name": {
+ "enable": "Show",
+ "disable": "Hide"
+ }
+ },
+ "open_lottery_image_folder": {
+ "name": "Prize Image Folder",
+ "description": "Manage prize image files; image filenames must match prize names"
+ },
+ "animation_music": {
+ "name": "Animation Music",
+ "description": "Set whether to play animation music",
+ "switchbutton_name": {
+ "enable": "Play",
+ "disable": "Off"
+ }
+ },
+ "result_music": {
+ "name": "Result Music",
+ "description": "Set whether to play result music",
+ "switchbutton_name": {
+ "enable": "Play",
+ "disable": "Off"
+ }
+ },
+ "open_animation_music_folder": {
+ "name": "Open Animation Music Folder",
+ "description": "Manage animation music files and enable random playback"
+ },
+ "open_result_music_folder": {
+ "name": "Open Result Music Folder",
+ "description": "Manage result music files and enable random playback"
+ },
+ "animation_music_volume": {
+ "name": "Animation Music Volume",
+ "description": "Set the volume for animation music"
+ },
+ "result_music_volume": {
+ "name": "Result Music Volume",
+ "description": "Set the volume for result music"
+ },
+ "animation_music_fade_in": {
+ "name": "Animation Music Fade-In Time",
+ "description": "Set the fade-in time for animation music"
+ },
+ "result_music_fade_in": {
+ "name": "Result Music Fade-In Time",
+ "description": "Set the fade-in time for result music"
+ },
+ "animation_music_fade_out": {
+ "name": "Animation Music Fade-Out Time",
+ "description": "Set the fade-out time for animation music"
+ },
+ "result_music_fade_out": {
+ "name": "Result Music Fade-Out Time",
+ "description": "Set the fade-out time for result music"
+ }
+ },
+ "safety_settings": {
+ "title": {
+ "name": "Safety Settings",
+ "description": "Safety and security related settings"
+ }
+ },
+ "basic_safety_settings": {
+ "title": {
+ "name": "Basic Safety Settings",
+ "description": "Basic safety settings"
+ },
+ "verification_method": {
+ "name": "Verification Method",
+ "description": "Set the verification method for security features"
+ },
+ "verification_process": {
+ "name": "Verification Process",
+ "description": "Security verification steps"
+ }
+ }
+}
diff --git a/app/resources/Language/ZH_CN.json b/app/resources/Language/ZH_CN.json
index 1cccfe24..b915af8c 100644
--- a/app/resources/Language/ZH_CN.json
+++ b/app/resources/Language/ZH_CN.json
@@ -1605,7 +1605,7 @@
"description": "选择Edge TTS语音",
"combo_items": [
"zh-CN-XiaoxiaoNeural",
- "zh-CN-YunxiNeural",
+ "zh-CN-YunxiNeural",
"zh-CN-XiaoyiNeural",
"en-US-JennyNeural",
"en-US-GuyNeural"
@@ -1702,4 +1702,4 @@
"description": "显示当前软件版本号"
}
}
-}
\ No newline at end of file
+}
diff --git a/app/resources/assets/FluentSystemIcons-Filled.json b/app/resources/assets/FluentSystemIcons-Filled.json
index c1f1a9fb..0401b068 100644
--- a/app/resources/assets/FluentSystemIcons-Filled.json
+++ b/app/resources/assets/FluentSystemIcons-Filled.json
@@ -9158,4 +9158,4 @@
"ic_fluent_table_freeze_column_dismiss_24_filled": 985796,
"ic_fluent_table_freeze_row_dismiss_20_filled": 985797,
"ic_fluent_table_freeze_row_dismiss_24_filled": 985798
-}
\ No newline at end of file
+}
diff --git a/app/resources/assets/light/ic_fluent_flash_20_filled_light.svg b/app/resources/assets/light/ic_fluent_flash_20_filled_light.svg
index 3e2da0a2..ed54feb5 100644
--- a/app/resources/assets/light/ic_fluent_flash_20_filled_light.svg
+++ b/app/resources/assets/light/ic_fluent_flash_20_filled_light.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/app/resources/font/LICENSE.txt b/app/resources/font/LICENSE.txt
index 211a701a..8b52e82c 100644
Binary files a/app/resources/font/LICENSE.txt and b/app/resources/font/LICENSE.txt differ
diff --git a/app/tools/config.py b/app/tools/config.py
new file mode 100644
index 00000000..6be4aea7
--- /dev/null
+++ b/app/tools/config.py
@@ -0,0 +1,687 @@
+"""
+通知工具模块
+提供便捷的InfoBar通知功能,用于在应用程序中显示重要的用户信息
+"""
+
+import os
+import json
+import glob
+from typing import Optional, Union
+from loguru import logger
+
+from PySide6.QtWidgets import QWidget
+from PySide6.QtCore import Qt
+from qfluentwidgets import InfoBar, InfoBarPosition, FluentIcon, InfoBarIcon
+
+
+from app.tools.path_utils import get_resources_path
+from app.tools.personalised import get_theme_icon
+from app.tools.settings_access import readme_settings_async
+from app.tools.list import get_student_list, get_group_list
+
+
+# ======= 通知工具函数 =======
+def show_success_notification(
+ title: str,
+ content: str,
+ parent: Optional[QWidget] = None,
+ duration: int = 3000,
+ position: Union[InfoBarPosition, str] = InfoBarPosition.TOP,
+ is_closable: bool = True,
+ orient: Qt.Orientation = Qt.Orientation.Horizontal,
+) -> InfoBar:
+ """
+ 显示成功通知
+
+ Args:
+ title: 通知标题
+ content: 通知内容
+ parent: 父窗口组件
+ duration: 显示时长(毫秒),-1表示永不消失
+ position: 显示位置,默认为顶部
+ is_closable: 是否可关闭
+ orient: 布局方向,默认为水平
+
+ Returns:
+ InfoBar实例
+ """
+ return InfoBar.success(
+ title=title,
+ content=content,
+ orient=orient,
+ isClosable=is_closable,
+ position=position,
+ duration=duration,
+ parent=parent,
+ )
+
+
+def show_warning_notification(
+ title: str,
+ content: str,
+ parent: Optional[QWidget] = None,
+ duration: int = -1,
+ position: Union[InfoBarPosition, str] = InfoBarPosition.BOTTOM,
+ is_closable: bool = True,
+ orient: Qt.Orientation = Qt.Orientation.Horizontal,
+) -> InfoBar:
+ """
+ 显示警告通知
+
+ Args:
+ title: 通知标题
+ content: 通知内容
+ parent: 父窗口组件
+ duration: 显示时长(毫秒),-1表示永不消失
+ position: 显示位置,默认为底部
+ is_closable: 是否可关闭
+ orient: 布局方向,默认为水平
+
+ Returns:
+ InfoBar实例
+ """
+ return InfoBar.warning(
+ title=title,
+ content=content,
+ orient=orient,
+ isClosable=is_closable,
+ position=position,
+ duration=duration,
+ parent=parent,
+ )
+
+
+def show_error_notification(
+ title: str,
+ content: str,
+ parent: Optional[QWidget] = None,
+ duration: int = 5000,
+ position: Union[InfoBarPosition, str] = InfoBarPosition.BOTTOM_RIGHT,
+ is_closable: bool = True,
+ orient: Qt.Orientation = Qt.Orientation.Vertical,
+) -> InfoBar:
+ """
+ 显示错误通知
+
+ Args:
+ title: 通知标题
+ content: 通知内容
+ parent: 父窗口组件
+ duration: 显示时长(毫秒),-1表示永不消失
+ position: 显示位置,默认为右下角
+ is_closable: 是否可关闭
+ orient: 布局方向,默认为垂直(适合长内容)
+
+ Returns:
+ InfoBar实例
+ """
+ return InfoBar.error(
+ title=title,
+ content=content,
+ orient=orient,
+ isClosable=is_closable,
+ position=position,
+ duration=duration,
+ parent=parent,
+ )
+
+
+def show_info_notification(
+ title: str,
+ content: str,
+ parent: Optional[QWidget] = None,
+ duration: int = -1,
+ position: Union[InfoBarPosition, str] = InfoBarPosition.BOTTOM_LEFT,
+ is_closable: bool = True,
+ orient: Qt.Orientation = Qt.Orientation.Horizontal,
+) -> InfoBar:
+ """
+ 显示信息通知
+
+ Args:
+ title: 通知标题
+ content: 通知内容
+ parent: 父窗口组件
+ duration: 显示时长(毫秒),-1表示永不消失
+ position: 显示位置,默认为左下角
+ is_closable: 是否可关闭
+ orient: 布局方向,默认为水平
+
+ Returns:
+ InfoBar实例
+ """
+ return InfoBar.info(
+ title=title,
+ content=content,
+ orient=orient,
+ isClosable=is_closable,
+ position=position,
+ duration=duration,
+ parent=parent,
+ )
+
+
+def show_custom_notification(
+ title: str,
+ content: str,
+ icon: Union[FluentIcon, InfoBarIcon, str] = InfoBarIcon.INFORMATION,
+ parent: Optional[QWidget] = None,
+ duration: int = 3000,
+ position: Union[InfoBarPosition, str] = InfoBarPosition.TOP,
+ is_closable: bool = True,
+ orient: Qt.Orientation = Qt.Orientation.Horizontal,
+ background_color: Optional[str] = None,
+ text_color: Optional[str] = None,
+) -> InfoBar:
+ """
+ 显示自定义通知
+
+ Args:
+ title: 通知标题
+ content: 通知内容
+ icon: 通知图标
+ parent: 父窗口组件
+ duration: 显示时长(毫秒),-1表示永不消失
+ position: 显示位置,默认为顶部
+ is_closable: 是否可关闭
+ orient: 布局方向,默认为水平
+ background_color: 背景颜色
+ text_color: 文本颜色
+
+ Returns:
+ InfoBar实例
+ """
+ info_bar = InfoBar.new(
+ icon=icon,
+ title=title,
+ content=content,
+ orient=orient,
+ isClosable=is_closable,
+ position=position,
+ duration=duration,
+ parent=parent,
+ )
+
+ if background_color and text_color:
+ info_bar.setCustomBackgroundColor(background_color, text_color)
+
+ return info_bar
+
+
+class NotificationConfig:
+ """通知配置类,用于定义通知的各种参数"""
+
+ def __init__(
+ self,
+ title: str = "",
+ content: str = "",
+ icon: Union[FluentIcon, InfoBarIcon, str] = None,
+ duration: int = 3000,
+ position: Union[InfoBarPosition, str] = InfoBarPosition.TOP,
+ is_closable: bool = True,
+ orient: Qt.Orientation = Qt.Orientation.Horizontal,
+ background_color: Optional[str] = None,
+ text_color: Optional[str] = None,
+ ):
+ self.title = title
+ self.content = content
+ self.icon = icon
+ self.duration = duration
+ self.position = position
+ self.is_closable = is_closable
+ self.orient = orient
+ self.background_color = background_color
+ self.text_color = text_color
+
+
+class NotificationType:
+ """预定义的通知类型"""
+
+ SUCCESS = "success"
+ WARNING = "warning"
+ ERROR = "error"
+ INFO = "info"
+ CUSTOM = "custom"
+
+
+def show_notification(
+ notification_type: str, config: NotificationConfig, parent: Optional[QWidget] = None
+) -> InfoBar:
+ """
+ 显示通知
+
+ Args:
+ notification_type: 通知类型,值为NotificationType中定义的常量
+ config: 通知配置对象
+ parent: 父窗口组件
+
+ Returns:
+ InfoBar实例
+ """
+ if notification_type == NotificationType.SUCCESS:
+ return InfoBar.success(
+ title=config.title,
+ content=config.content,
+ orient=config.orient,
+ isClosable=config.is_closable,
+ position=config.position,
+ duration=config.duration,
+ parent=parent,
+ )
+ elif notification_type == NotificationType.WARNING:
+ return InfoBar.warning(
+ title=config.title,
+ content=config.content,
+ orient=config.orient,
+ isClosable=config.is_closable,
+ position=config.position,
+ duration=config.duration,
+ parent=parent,
+ )
+ elif notification_type == NotificationType.ERROR:
+ return InfoBar.error(
+ title=config.title,
+ content=config.content,
+ orient=config.orient,
+ isClosable=config.is_closable,
+ position=config.position,
+ duration=config.duration,
+ parent=parent,
+ )
+ elif notification_type == NotificationType.INFO:
+ return InfoBar.info(
+ title=config.title,
+ content=config.content,
+ orient=config.orient,
+ isClosable=config.is_closable,
+ position=config.position,
+ duration=config.duration,
+ parent=parent,
+ )
+ elif notification_type == NotificationType.CUSTOM:
+ info_bar = InfoBar.new(
+ icon=config.icon or InfoBarIcon.INFORMATION,
+ title=config.title,
+ content=config.content,
+ orient=config.orient,
+ isClosable=config.is_closable,
+ position=config.position,
+ duration=config.duration,
+ parent=parent,
+ )
+
+ if config.background_color and config.text_color:
+ info_bar.setCustomBackgroundColor(
+ config.background_color, config.text_color
+ )
+
+ return info_bar
+ else:
+ raise ValueError(f"不支持的通知类型: {notification_type}")
+
+
+# ======= 获取清除记录前缀 =======
+def check_clear_record():
+ """检查是否需要清除已抽取记录"""
+ clear_record = readme_settings_async("roll_call_settings", "clear_record")
+ if clear_record == 0: # 重启后清除
+ prefix = "all"
+ elif clear_record == 1: # 直至全部抽取完
+ prefix = "until"
+ return prefix
+
+
+# ======= 记录已抽取的学生 =======
+def record_drawn_student(class_name: str, gender: str, group: str, student_name):
+ """记录已抽取的学生名称和次数
+
+ Args:
+ class_name: 班级名称
+ gender: 性别
+ group: 分组
+ student_name: 学生名称或学生列表
+ """
+ # 构建文件路径,与remove_record保持一致
+ file_path = get_resources_path(
+ "TEMP", f"draw_until_{class_name}_{gender}_{group}.json"
+ )
+
+ # 确保目录存在
+ os.makedirs(os.path.dirname(file_path), exist_ok=True)
+
+ # 读取现有记录
+ drawn_records = _load_drawn_records(file_path)
+
+ # 提取学生名称列表
+ students_to_add = _extract_student_names(student_name)
+
+ # 更新学生抽取次数
+ updated_students = []
+ for name in students_to_add:
+ if name in drawn_records:
+ # 学生已存在,增加抽取次数
+ drawn_records[name] += 1
+ updated_students.append(f"{name}(第{drawn_records[name]}次)")
+ else:
+ # 新学生,初始化抽取次数为1
+ drawn_records[name] = 1
+ updated_students.append(f"{name}(第1次)")
+
+ # 保存更新后的记录
+ if updated_students:
+ _save_drawn_records(file_path, drawn_records)
+ logger.debug(f"已记录学生/小组: {', '.join(updated_students)}")
+ else:
+ logger.debug("没有新的学生需要记录")
+
+
+def _load_drawn_records(file_path: str) -> dict:
+ """从文件加载已抽取的学生记录
+
+ Args:
+ file_path: 记录文件路径
+
+ Returns:
+ 已抽取的学生记录字典,键为学生名称,值为抽取次数
+ """
+ if not os.path.exists(file_path):
+ return {}
+
+ try:
+ with open(file_path, "r", encoding="utf-8") as file:
+ data = json.load(file)
+
+ drawn_records = {}
+
+ # 处理不同的数据结构
+ if isinstance(data, dict):
+ # 新格式:字典,键为学生名称,值为抽取次数
+ for name, count in data.items():
+ if isinstance(name, str) and isinstance(count, int):
+ drawn_records[name] = count
+ elif isinstance(data, list):
+ # 旧格式:列表,只包含学生名称
+ for item in data:
+ if isinstance(item, str):
+ # 兼容旧格式,初始化抽取次数为1
+ drawn_records[item] = 1
+ elif isinstance(item, dict) and "name" in item:
+ # 兼容可能的字典格式
+ name = item["name"]
+ count = item.get("count", 1) # 默认次数为1
+ if isinstance(name, str) and isinstance(count, int):
+ drawn_records[name] = count
+ elif isinstance(data, dict) and "drawn_names" in data:
+ # 兼容可能的字典格式
+ for item in data["drawn_names"]:
+ if isinstance(item, str):
+ drawn_records[item] = 1
+ elif isinstance(item, dict) and "name" in item:
+ name = item["name"]
+ count = item.get("count", 1)
+ if isinstance(name, str) and isinstance(count, int):
+ drawn_records[name] = count
+
+ return drawn_records
+ except (json.JSONDecodeError, IOError) as e:
+ logger.error(f"读取已抽取记录失败: {e}")
+ return {}
+
+
+def _extract_student_names(student_name) -> list:
+ """从不同类型的学生名称参数中提取学生名称列表
+
+ Args:
+ student_name: 学生名称或学生列表
+
+ Returns:
+ 学生名称列表
+ """
+ if isinstance(student_name, str):
+ # 单个学生名称
+ return [student_name]
+
+ if isinstance(student_name, list):
+ # 学生列表,可能是元组列表或字符串列表
+ names = []
+ for item in student_name:
+ if isinstance(item, str):
+ names.append(item)
+ elif isinstance(item, tuple) and len(item) >= 2:
+ names.append(item[1])
+ return names
+
+ if isinstance(student_name, tuple) and len(student_name) >= 2:
+ # 单个元组,提取第二个元素(名称)
+ return [student_name[1]]
+
+ return []
+
+
+def _save_drawn_records(file_path: str, drawn_records: dict) -> None:
+ """保存已抽取的学生记录到文件
+
+ Args:
+ file_path: 记录文件路径
+ drawn_records: 已抽取的学生记录字典,键为学生名称,值为抽取次数
+ """
+ try:
+ with open(file_path, "w", encoding="utf-8") as file:
+ json.dump(drawn_records, file, ensure_ascii=False, indent=2)
+ except IOError as e:
+ logger.error(f"保存已抽取记录失败: {e}")
+
+
+# ======= 读取已抽取记录 =======
+def read_drawn_record(class_name: str, gender: str, group: str):
+ """读取已抽取记录"""
+ file_path = get_resources_path(
+ "TEMP", f"draw_until_{class_name}_{gender}_{group}.json"
+ )
+ if os.path.exists(file_path):
+ try:
+ with open(file_path, "r", encoding="utf-8") as file:
+ data = json.load(file)
+
+ # 处理不同的数据结构
+ if isinstance(data, dict):
+ # 转换为列表格式,每个元素为(名称, 次数)元组
+ drawn_records = [(name, count) for name, count in data.items()]
+ elif isinstance(data, list):
+ # 转换为列表格式,每个元素为(名称, 1)元组
+ drawn_records = []
+ for item in data:
+ if isinstance(item, str):
+ drawn_records.append((item, 1))
+ elif isinstance(item, dict) and "name" in item:
+ name = item["name"]
+ count = item.get("count", 1)
+ drawn_records.append((name, count))
+ elif isinstance(data, dict) and "drawn_names" in data:
+ # 兼容可能的字典格式
+ drawn_records = []
+ for item in data["drawn_names"]:
+ if isinstance(item, str):
+ drawn_records.append((item, 1))
+ elif isinstance(item, dict) and "name" in item:
+ name = item["name"]
+ count = item.get("count", 1)
+ drawn_records.append((name, count))
+ else:
+ drawn_records = []
+
+ logger.debug(f"已读取{class_name}_{gender}_{group}已抽取记录")
+ return drawn_records
+ except (json.JSONDecodeError, IOError) as e:
+ logger.error(f"读取已抽取记录失败: {e}")
+ return []
+ else:
+ logger.debug(f"文件 {file_path} 不存在")
+ return []
+
+
+# ======= 重置已抽取记录 =======
+def remove_record(class_name: str, gender: str, group: str, _prefix: str = "0"):
+ """清除已抽取记录"""
+ prefix = check_clear_record()
+ if prefix == "all" and _prefix == "restart":
+ prefix = "restart"
+
+ logger.debug(f"清除记录前缀: {prefix}, _prefix: {_prefix}")
+
+ if prefix == "all":
+ # 构建搜索模式,匹配所有前缀的文件夹
+ search_pattern = os.path.join(
+ "app", "resources", "TEMP", f"draw_*_{class_name}_{gender}_{group}.json"
+ )
+
+ # 查找所有匹配的文件
+ file_list = glob.glob(search_pattern)
+
+ # 删除找到的文件
+ for file_path in file_list:
+ try:
+ os.remove(file_path)
+ file_name = os.path.basename(os.path.dirname(file_path))
+ logger.info(f"已删除记录文件夹: {file_name}")
+ except OSError as e:
+ logger.error(f"删除文件{file_path}失败: {e}")
+ elif prefix == "until":
+ # 只删除特定前缀的文件
+ file_path = get_resources_path(
+ "TEMP", f"draw_{prefix}_{class_name}_{gender}_{group}.json"
+ )
+ try:
+ if os.path.exists(file_path):
+ os.remove(file_path)
+ file_name = os.path.basename(os.path.dirname(file_path))
+ logger.info(f"已删除记录文件夹: {file_name}")
+ except OSError as e:
+ logger.error(f"删除文件{file_path}失败: {e}")
+ elif prefix == "restart": # 重启后清除
+ # 构建搜索模式,匹配所有前缀的文件夹
+ search_pattern = os.path.join("app", "resources", "TEMP", "draw_*.json")
+ # 查找所有匹配的文件
+ file_list = glob.glob(search_pattern)
+ # 删除找到的文件
+ for file_path in file_list:
+ try:
+ os.remove(file_path)
+ file_name = os.path.basename(os.path.dirname(file_path))
+ logger.info(f"已删除记录文件夹: {file_name}")
+ except OSError as e:
+ logger.error(f"删除文件{file_path}失败: {e}")
+
+
+def reset_drawn_record(self, class_name: str, gender: str, group: str):
+ """删除已抽取记录文件"""
+ clear_record = readme_settings_async("roll_call_settings", "clear_record")
+ if clear_record in [0, 1]: # 重启后清除、直至全部抽取完
+ remove_record(class_name, gender, group)
+ show_notification(
+ NotificationType.INFO,
+ NotificationConfig(
+ title="提示",
+ content=f"已重置{class_name}已抽取记录",
+ icon=FluentIcon.INFO,
+ ),
+ parent=self,
+ )
+ logger.info(f"已重置{class_name}_{gender}_{group}已抽取记录")
+ else: # 重复抽取
+ show_notification(
+ NotificationType.INFO,
+ NotificationConfig(
+ title="提示",
+ content=f"当前处于重复抽取状态,无需清除{class_name}已抽取记录",
+ icon=get_theme_icon("ic_fluent_warning_20_filled"),
+ ),
+ parent=self,
+ )
+ logger.info(
+ f"当前处于重复抽取状态,无需清除{class_name}_{gender}_{group}已抽取记录"
+ )
+
+
+# ======= 计算剩余人数 =======
+def calculate_remaining_count(
+ half_repeat: int,
+ class_name: str,
+ gender_filter: str,
+ group_index: int,
+ group_filter: str,
+ total_count: int,
+):
+ """根据half_repeat设置计算实际剩余人数或组数
+
+ Args:
+ half_repeat: 重复抽取次数
+ class_name: 班级名称
+ gender_filter: 性别筛选条件
+ group_index: 分组索引
+ group_filter: 分组筛选条件
+ total_count: 总人数或总组数
+
+ Returns:
+ 实际剩余人数或组数
+ """
+ # 根据half_repeat设置计算实际剩余人数
+ if half_repeat > 0: # 只有当设置值大于0时才计算排除后的剩余人数
+ # 读取已抽取记录
+ drawn_records = read_drawn_record(class_name, gender_filter, group_filter)
+ # 将记录转换为字典格式,便于快速查找
+ drawn_counts = {}
+ for record in drawn_records:
+ if isinstance(record, tuple) and len(record) >= 2:
+ # 处理元组格式:(名称, 次数)
+ name, count = record[0], record[1]
+ drawn_counts[name] = count
+ elif isinstance(record, dict) and "name" in record:
+ # 处理字典格式:{'name': 名称, 'count': 次数}
+ name = record["name"]
+ count = record.get("count", 1)
+ drawn_counts[name] = count
+
+ # 处理小组模式
+ if group_index == 1: # 全部小组
+ # 获取所有小组列表
+ group_list = get_group_list(class_name)
+
+ # 计算已被排除的小组数量
+ excluded_count = 0
+ for group_name in group_list:
+ # 如果小组已被抽取次数达到或超过设置值,则计入排除数量
+ if (
+ group_name in drawn_counts
+ and drawn_counts[group_name] >= half_repeat
+ ):
+ excluded_count += 1
+
+ # 计算实际剩余组数
+ return max(0, len(group_list) - excluded_count)
+ else:
+ # 处理学生模式
+ # 计算已被排除的学生数量
+ excluded_count = 0
+ # 获取当前班级的学生列表
+ student_list = get_student_list(class_name)
+ for student in student_list:
+ # 从学生字典中提取姓名
+ student_name = (
+ student["name"]
+ if isinstance(student, dict) and "name" in student
+ else student
+ )
+
+ # 如果学生已被抽取次数达到或超过设置值,则计入排除数量
+ if (
+ student_name in drawn_counts
+ and drawn_counts[student_name] >= half_repeat
+ ):
+ excluded_count += 1
+
+ # 计算实际剩余人数
+ return max(0, total_count - excluded_count)
+ else:
+ # 如果half_repeat为0,则不排除任何学生或小组
+ return total_count
diff --git a/app/tools/extract.py b/app/tools/extract.py
index dbee2218..b54b6a7d 100644
--- a/app/tools/extract.py
+++ b/app/tools/extract.py
@@ -1,27 +1,28 @@
# ==================================================
# 导入模块
# ==================================================
-from qfluentwidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtWidgets import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
+from qfluentwidgets import *
+from PySide6.QtGui import *
+from PySide6.QtWidgets import *
+from PySide6.QtCore import *
+from PySide6.QtNetwork import *
import json
-from typing import Dict, Any
+from typing import Dict
from loguru import logger
-from PyQt6.QtCore import QDateTime
+from PySide6.QtCore import QDateTime
from app.tools.path_utils import *
+
# ==================================================
# 判断当前时间是否在非上课时间段
# ==================================================
def _is_non_class_time() -> bool:
"""检测当前时间是否在非上课时间段
-
+
当'课间禁用'开关启用时,用于判断是否需要安全验证
-
+
Returns:
bool: 如果当前时间在非上课时间段内返回True,否则返回False
"""
@@ -29,18 +30,18 @@ def _is_non_class_time() -> bool:
# 1. 检查课间禁用开关是否启用
if not _is_instant_draw_disable_enabled():
return False
-
+
# 2. 获取非上课时间段配置
non_class_times = _get_non_class_times_config()
if not non_class_times:
return False
-
+
# 3. 获取当前时间并转换为总秒数
current_total_seconds = _get_current_time_in_seconds()
-
+
# 4. 检查当前时间是否在任何非上课时间段内
return _is_time_in_ranges(current_total_seconds, non_class_times)
-
+
except Exception as e:
logger.error(f"检测非上课时间失败: {e}")
return False
@@ -48,7 +49,7 @@ def _is_non_class_time() -> bool:
def _is_instant_draw_disable_enabled() -> bool:
"""检查课间禁用开关是否启用
-
+
Returns:
bool: 如果课间禁用开关启用返回True,否则返回False
"""
@@ -56,13 +57,13 @@ def _is_instant_draw_disable_enabled() -> bool:
settings_path = get_settings_path()
if not file_exists(settings_path):
return False
-
- with open_file(settings_path, 'r', encoding='utf-8') as f:
+
+ with open_file(settings_path, "r", encoding="utf-8") as f:
settings = json.load(f)
-
+
program_functionality = settings.get("program_functionality", {})
return program_functionality.get("instant_draw_disable", False)
-
+
except Exception as e:
logger.error(f"读取课间禁用设置失败: {e}")
return False
@@ -70,7 +71,7 @@ def _is_instant_draw_disable_enabled() -> bool:
def _get_non_class_times_config() -> Dict[str, str]:
"""获取非上课时间段配置
-
+
Returns:
Dict[str, str]: 非上课时间段配置字典,如果获取失败返回空字典
"""
@@ -78,12 +79,12 @@ def _get_non_class_times_config() -> Dict[str, str]:
time_settings_path = get_settings_path()
if not file_exists(time_settings_path):
return {}
-
- with open_file(time_settings_path, 'r', encoding='utf-8') as f:
+
+ with open_file(time_settings_path, "r", encoding="utf-8") as f:
time_settings = json.load(f)
-
- return time_settings.get('non_class_times', {})
-
+
+ return time_settings.get("non_class_times", {})
+
except Exception as e:
logger.error(f"读取时间设置失败: {e}")
return {}
@@ -91,7 +92,7 @@ def _get_non_class_times_config() -> Dict[str, str]:
def _get_current_time_in_seconds() -> int:
"""获取当前时间并转换为总秒数
-
+
Returns:
int: 当前时间的总秒数(从午夜开始计算)
"""
@@ -99,65 +100,67 @@ def _get_current_time_in_seconds() -> int:
current_hour = current_time.time().hour()
current_minute = current_time.time().minute()
current_second = current_time.time().second()
-
+
return current_hour * 3600 + current_minute * 60 + current_second
def _is_time_in_ranges(current_seconds: int, time_ranges: Dict[str, str]) -> bool:
"""检查当前时间是否在任何一个时间范围内
-
+
Args:
current_seconds: 当前时间的总秒数
time_ranges: 时间范围字典,格式为 {"name": "HH:MM:SS-HH:MM:SS"}
-
+
Returns:
bool: 如果当前时间在任何一个时间范围内返回True,否则返回False
"""
for range_name, time_range in time_ranges.items():
try:
- start_end = time_range.split('-')
+ start_end = time_range.split("-")
if len(start_end) != 2:
logger.warning(f"时间范围格式错误: {range_name} = {time_range}")
continue
-
+
start_time_str, end_time_str = start_end
-
+
# 解析开始时间
start_total_seconds = _parse_time_string_to_seconds(start_time_str)
-
+
# 解析结束时间
end_total_seconds = _parse_time_string_to_seconds(end_time_str)
-
+
# 检查当前时间是否在该非上课时间段内
if start_total_seconds <= current_seconds < end_total_seconds:
return True
-
+
except Exception as e:
- logger.error(f"解析非上课时间段失败: {range_name} = {time_range}, 错误: {e}")
+ logger.error(
+ f"解析非上课时间段失败: {range_name} = {time_range}, 错误: {e}"
+ )
continue
-
+
return False
def _parse_time_string_to_seconds(time_str: str) -> int:
"""将时间字符串转换为总秒数
-
+
Args:
time_str: 时间字符串,格式为 "HH:MM:SS" 或 "HH:MM"
-
+
Returns:
int: 时间的总秒数
-
+
Raises:
ValueError: 如果时间字符串格式不正确
"""
- time_parts = list(map(int, time_str.split(':')))
-
+ time_parts = list(map(int, time_str.split(":")))
+
if len(time_parts) < 2 or len(time_parts) > 3:
raise ValueError(f"时间字符串格式不正确: {time_str}")
-
+
hours = time_parts[0]
minutes = time_parts[1]
seconds = time_parts[2] if len(time_parts) > 2 else 0
-
- return hours * 3600 + minutes * 60 + seconds
\ No newline at end of file
+
+ return hours * 3600 + minutes * 60 + seconds
diff --git a/app/tools/history.py b/app/tools/history.py
index b654cd1a..be4b583f 100644
--- a/app/tools/history.py
+++ b/app/tools/history.py
@@ -2,16 +2,16 @@
# 导入库
# ==================================================
import json
-import os
+import math
from datetime import datetime
from typing import Dict, List, Any, Optional, Union
from pathlib import Path
from loguru import logger
-from PyQt6.QtWidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from PySide6.QtNetwork import *
from qfluentwidgets import *
from app.tools.variable import *
@@ -22,17 +22,22 @@
from app.Language.obtain_language import *
from app.tools.list import *
+from random import SystemRandom
+
+system_random = SystemRandom()
+
# ==================================================
-# 历史记录处理函数
+# 历史记录文件路径处理函数
# ==================================================
+
def get_history_file_path(history_type: str, file_name: str) -> Path:
"""获取历史记录文件路径
-
+
Args:
history_type: 历史记录类型 (roll_call, lottery 等)
file_name: 文件名(不含扩展名)
-
+
Returns:
Path: 历史记录文件路径
"""
@@ -40,54 +45,62 @@ def get_history_file_path(history_type: str, file_name: str) -> Path:
history_dir.mkdir(parents=True, exist_ok=True)
return history_dir / f"{file_name}.json"
+
+# ==================================================
+# 历史记录数据读写函数
+# ==================================================
+
+
def load_history_data(history_type: str, file_name: str) -> Dict[str, Any]:
"""加载历史记录数据
-
+
Args:
history_type: 历史记录类型 (roll_call, lottery 等)
file_name: 文件名(不含扩展名)
-
+
Returns:
Dict[str, Any]: 历史记录数据
"""
file_path = get_history_file_path(history_type, file_name)
-
+
if not file_path.exists():
return {}
-
+
try:
- with open(file_path, 'r', encoding='utf-8') as f:
+ with open(file_path, "r", encoding="utf-8") as f:
return json.load(f)
except Exception as e:
logger.error(f"加载历史记录数据失败: {e}")
return {}
+
def save_history_data(history_type: str, file_name: str, data: Dict[str, Any]) -> bool:
"""保存历史记录数据
-
+
Args:
history_type: 历史记录类型 (roll_call, lottery 等)
file_name: 文件名(不含扩展名)
data: 要保存的数据
-
+
Returns:
bool: 保存是否成功
"""
file_path = get_history_file_path(history_type, file_name)
try:
- with open(file_path, 'w', encoding='utf-8') as f:
+ with open(file_path, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=4)
return True
except Exception as e:
logger.error(f"保存历史记录数据失败: {e}")
return False
+
def get_all_history_names(history_type: str) -> List[str]:
"""获取所有历史记录名称列表
-
+
Args:
history_type: 历史记录类型 (roll_call, lottery 等)
-
+
Returns:
List[str]: 历史记录名称列表
"""
@@ -103,79 +116,19 @@ def get_all_history_names(history_type: str) -> List[str]:
logger.error(f"获取历史记录名称列表失败: {e}")
return []
-def calculate_weight(students: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
- """计算学生的权重
-
- Args:
- students: 学生列表
- base_weight: 基础权重值
-
- Returns:
- List[Dict[str, Any]]: 包含权重的学生列表
- """
- base_weight = 1.0
-
- total_draws = sum(student.get('total_count', 0) for student in students)
- if total_draws == 0:
- for student in students:
- student['next_weight'] = base_weight
- return students
-
- max_count = max(student.get('total_count', 0) for student in students)
- for student in students:
- count = student.get('total_count', 0)
- student['next_weight'] = (max_count - count + 1) * base_weight
-
- return students
-
-def format_table_item(value: Union[str, int, float], is_percentage: bool = False) -> str:
- """格式化表格项显示值
-
- Args:
- value: 要格式化的值
- is_percentage: 是否为百分比值
-
- Returns:
- str: 格式化后的字符串
- """
- if isinstance(value, (int, float)):
- if is_percentage:
- return f"{value:.2%}"
- else:
- return f"{value:.2f}"
- return str(value)
-def create_table_item(value: Union[str, int, float],
- font_size: int = 12,
- is_centered: bool = True,
- is_percentage: bool = False) -> 'QTableWidgetItem':
- """创建表格项
-
- Args:
- value: 要显示的值
- font_size: 字体大小
- is_centered: 是否居中
- is_percentage: 是否为百分比值
-
- Returns:
- QTableWidgetItem: 表格项对象
- """
- display_value = format_table_item(value, is_percentage)
- item = QTableWidgetItem(display_value)
- from app.tools.personalised import load_custom_font
- item.setFont(QFont(load_custom_font(), font_size))
- if is_centered:
- item.setTextAlignment(Qt.AlignmentFlag.AlignCenter | Qt.AlignmentFlag.AlignVCenter)
- item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsEditable)
- return item
+# ==================================================
+# 历史记录统计函数
+# ==================================================
+
def get_name_history(history_type: str, class_name: str) -> int:
"""获取指定班级的名称历史记录数量
-
+
Args:
history_type: 历史记录类型 (roll_call, lottery 等)
class_name: 班级名称/奖池名称
-
+
Returns:
int: 名称历史记录数量
"""
@@ -188,13 +141,14 @@ def get_name_history(history_type: str, class_name: str) -> int:
else:
return 0
+
def get_draw_sessions_history(history_type: str, class_name: str) -> int:
"""获取指定班级的抽取会话历史记录数量
-
+
Args:
history_type: 历史记录类型 (roll_call, lottery 等)
class_name: 班级名称
-
+
Returns:
int: 抽取会话历史记录数量
"""
@@ -214,14 +168,17 @@ def get_draw_sessions_history(history_type: str, class_name: str) -> int:
session_count += len(session_list)
return session_count
-def get_individual_statistics(history_type: str, class_name: str, students_name: str) -> int:
+
+def get_individual_statistics(
+ history_type: str, class_name: str, students_name: str
+) -> int:
"""获取指定班级的个人统计记录数量
-
+
Args:
history_type: 历史记录类型 (roll_call, lottery 等)
class_name: 班级名称/奖池名称
students_name: 学生姓名/奖品名称
-
+
Returns:
int: 个人统计记录数量
"""
@@ -243,140 +200,491 @@ def get_individual_statistics(history_type: str, class_name: str, students_name:
return 0
return len(student_history)
-def save_roll_call_history(class_name: str, students: List[Dict[str, Any]],
- selected_students: List[Dict[str, Any]],
- mode: str = "随机", count: int = 1,
- random_method: str = "uniform",
- probability_weight_method: str = "equal") -> bool:
+
+# ==================================================
+# 权重格式化函数
+# ==================================================
+def format_weight_for_display(weights_data: list, weight_key: str = "weight") -> tuple:
+ """格式化权重显示,确保小数点对齐
+
+ Args:
+ weights_data: 包含权重数据的列表
+ weight_key: 权重在数据项中的键名,默认为'weight'
+
+ Returns:
+ tuple: (格式化函数, 整数部分最大长度, 小数部分最大长度)
+ """
+ # 计算权重显示的最大长度,考虑小数点前后的对齐
+ max_int_length = 0 # 整数部分最大长度
+ max_dec_length = 2 # 固定为两位小数
+
+ for item in weights_data:
+ weight = item.get(weight_key, 0)
+ weight_str = str(weight)
+ if "." in weight_str:
+ int_part, _ = weight_str.split(".", 1)
+ max_int_length = max(max_int_length, len(int_part))
+ else:
+ max_int_length = max(max_int_length, len(weight_str))
+
+ # 格式化权重显示,确保小数点对齐并保留两位小数
+ def format_weight(weight):
+ weight_str = str(weight)
+ if "." in weight_str:
+ int_part, dec_part = weight_str.split(".", 1)
+ # 确保小数部分有两位
+ if len(dec_part) < 2:
+ dec_part = dec_part.ljust(2, "0")
+ elif len(dec_part) > 2:
+ dec_part = dec_part[:2] # 截断多余的小数位
+ # 整数部分右对齐,小数部分左对齐
+ formatted_int = int_part.rjust(max_int_length)
+ # 小数部分补齐到最大长度
+ formatted_dec = dec_part.ljust(max_dec_length)
+ return f"{formatted_int}.{formatted_dec}"
+ else:
+ # 没有小数点的情况,添加小数点和两位小数
+ formatted_int = weight_str.rjust(max_int_length)
+ formatted_dec = "00".ljust(max_dec_length)
+ return f"{formatted_int}.{formatted_dec}"
+
+ return format_weight, max_int_length, max_dec_length
+
+
+# ==================================================
+# 公平抽取权重计算函数
+# ==================================================
+def calculate_weight(students_data: list, class_name: str) -> list:
+ """计算学生权重
+
+ Args:
+ students_data: 学生数据列表
+ class_name: 班级名称
+
+ Returns:
+ list: 更新后的学生数据列表,包含权重信息
+ """
+ # 从设置中加载权重相关配置
+ settings = {
+ "fair_draw_enabled": readme_settings_async("advanced_settings", "fair_draw"),
+ "fair_draw_group_enabled": readme_settings_async(
+ "advanced_settings", "fair_draw_group"
+ ),
+ "fair_draw_gender_enabled": readme_settings_async(
+ "advanced_settings", "fair_draw_gender"
+ ),
+ "fair_draw_time_enabled": readme_settings_async(
+ "advanced_settings", "fair_draw_time"
+ ),
+ "base_weight": readme_settings_async("advanced_settings", "base_weight"),
+ "min_weight": readme_settings_async("advanced_settings", "min_weight"),
+ "max_weight": readme_settings_async("advanced_settings", "max_weight"),
+ }
+
+ # 加载历史记录数据
+ history_data = load_history_data("roll_call", class_name)
+
+ # 冷启动轮次配置
+ COLD_START_ROUNDS = 10
+ current_stats = history_data.get("total_stats", 0)
+ is_cold_start = current_stats < COLD_START_ROUNDS
+
+ # 初始化权重数据结构
+ weight_data = {}
+ for student in students_data:
+ student_id = student.get("id", student.get("name", ""))
+ weight_data[student_id] = {
+ "total_count": 0,
+ "group_count": 0,
+ "gender_count": 0,
+ "last_drawn_time": None,
+ "rounds_missed": 0,
+ }
+
+ # 从历史记录中提取权重信息
+ if isinstance(history_data, dict) and "students" in history_data:
+ students_history = history_data.get("students", {})
+ if isinstance(students_history, dict):
+ for student_name, student_info in students_history.items():
+ if student_name in weight_data and isinstance(student_info, dict):
+ # 更新总计数和最后抽取时间
+ weight_data[student_name]["total_count"] = student_info.get(
+ "total_count", 0
+ )
+ weight_data[student_name]["rounds_missed"] = student_info.get(
+ "rounds_missed", 0
+ )
+
+ last_drawn_time = student_info.get("last_drawn_time", "")
+ if last_drawn_time:
+ weight_data[student_name]["last_drawn_time"] = last_drawn_time
+
+ # 从历史记录中获取小组和性别信息
+ history = student_info.get("history", [])
+ if isinstance(history, list):
+ for record in history:
+ if isinstance(record, dict):
+ # 更新小组计数
+ draw_group = record.get("draw_group", "")
+ if (
+ draw_group
+ and draw_group
+ != get_content_combo_name_async(
+ "roll_call", "range_combobox"
+ )[0]
+ ):
+ weight_data[student_name]["group_count"] += 1
+
+ # 更新性别计数
+ draw_gender = record.get("draw_gender", "")
+ if (
+ draw_gender
+ and draw_gender
+ != get_content_combo_name_async(
+ "roll_call", "gender_combobox"
+ )[0]
+ ):
+ weight_data[student_name]["gender_count"] += 1
+
+ # 获取小组和性别统计
+ group_stats = history_data.get("group_stats", {})
+ gender_stats = history_data.get("gender_stats", {})
+
+ # 为每个学生计算权重
+ for student in students_data:
+ student_id = student.get("id", student.get("name", ""))
+ if student_id not in weight_data:
+ continue
+
+ student_weight_data = weight_data[student_id]
+
+ # 计算频率惩罚因子 - 采用更平滑的平方根倒数函数
+ if settings["fair_draw_enabled"]:
+ # 使用平方根倒数函数,避免除零错误,提供更平滑的权重变化
+ frequency_factor = 1.0 / math.sqrt(
+ student_weight_data["total_count"] * 2 + 1
+ )
+
+ # 冷启动特殊处理 - 防止新学生权重过低
+ if is_cold_start:
+ frequency_factor = min(0.8, frequency_factor)
+
+ # 转换为与新实现兼容的权重值
+ frequency_penalty = frequency_factor * 3.0
+ else:
+ frequency_penalty = 0.0
+
+ # 计算小组平衡因子 - 采用倒数函数
+ if settings["fair_draw_group_enabled"]:
+ # 获取学生当前小组
+ current_student_group = student.get("group", "")
+
+ # 获取有效小组统计数量(值>0的条目)
+ valid_groups = [v for v in group_stats.values() if v > 0]
+
+ if len(valid_groups) > 3: # 有效小组数量达标
+ group_history = max(group_stats.get(current_student_group, 0), 0)
+ group_factor = 1.0 / (group_history * 0.2 + 1)
+ group_balance = group_factor * 0.8
+ else:
+ # 数据不足时,使用相对计数方法
+ all_counts = [data["group_count"] for data in weight_data.values()]
+ max_group_count = max(all_counts) if all_counts else 0
+
+ if max_group_count == 0:
+ group_balance = (
+ 0.2 # 给所有学生一个小组平衡的基础权重提升,保持公平
+ )
+ elif student_weight_data["group_count"] == 0:
+ group_balance = 0.5 # 给从未在小组中被选中的学生一个基础权重提升
+ else:
+ group_balance = 0.8 * (
+ 1.0 - (student_weight_data["group_count"] / max_group_count)
+ )
+ else:
+ group_balance = 0.0
+
+ # 计算性别平衡因子 - 采用倒数函数
+ if settings["fair_draw_gender_enabled"]:
+ # 获取学生当前性别
+ current_student_gender = student.get("gender", "")
+
+ # 获取有效性别统计数量(值>0的条目)
+ valid_gender = [v for v in gender_stats.values() if v > 0]
+
+ if len(valid_gender) > 3: # 有效性别数量达标
+ gender_history = max(gender_stats.get(current_student_gender, 0), 0)
+ gender_factor = 1.0 / (gender_history * 0.2 + 1)
+ gender_balance = gender_factor * 0.8
+ else:
+ # 数据不足时,使用相对计数方法
+ all_counts = [data["gender_count"] for data in weight_data.values()]
+ max_gender_count = max(all_counts) if all_counts else 0
+
+ if max_gender_count == 0:
+ gender_balance = (
+ 0.2 # 给所有学生一个性别平衡的基础权重提升,保持公平
+ )
+ elif student_weight_data["gender_count"] == 0:
+ gender_balance = (
+ 0.5 # 给从未在性别平衡中被选中的学生一个基础权重提升
+ )
+ else:
+ gender_balance = 0.8 * (
+ 1.0 - (student_weight_data["gender_count"] / max_gender_count)
+ )
+ else:
+ gender_balance = 0.0
+
+ # 计算时间因子
+ if (
+ settings["fair_draw_time_enabled"]
+ and student_weight_data["last_drawn_time"]
+ ):
+ try:
+ from datetime import datetime
+
+ last_time = datetime.fromisoformat(
+ student_weight_data["last_drawn_time"]
+ )
+ current_time = datetime.now()
+ days_diff = (current_time - last_time).days
+ time_factor = min(1.0, days_diff / 30.0) * 0.5
+ except:
+ time_factor = 0.0
+ else:
+ time_factor = 0.0
+
+ # 计算总权重 - 采用旧实现的权重分配方式
+ student_weights = {
+ "base": settings["base_weight"] * 0.2, # 基础权重占比20%
+ "frequency": frequency_penalty, # 频率惩罚
+ "group": group_balance, # 小组平衡
+ "gender": gender_balance, # 性别平衡
+ "time": time_factor, # 时间因子
+ }
+
+ total_weight = sum(student_weights.values())
+
+ # 确保权重在最小和最大值之间
+ total_weight = max(
+ settings["min_weight"], min(settings["max_weight"], total_weight)
+ )
+ total_weight = round(total_weight, 2)
+
+ # 设置学生的权重和详细信息
+ student["next_weight"] = total_weight
+ student["weight_details"] = {
+ "base_weight": student_weights["base"],
+ "frequency_penalty": student_weights["frequency"],
+ "group_balance": student_weights["group"],
+ "gender_balance": student_weights["gender"],
+ "time_factor": student_weights["time"],
+ "total_weight": total_weight,
+ "is_cold_start": is_cold_start,
+ }
+
+ return students_data
+
+
+# ==================================================
+# 历史记录保存与更新函数
+# ==================================================
+def save_roll_call_history(
+ class_name: str,
+ selected_students: List[Dict[str, Any]],
+ group_filter: Optional[str] = None,
+ gender_filter: Optional[str] = None,
+) -> bool:
"""保存点名历史记录
-
+
Args:
class_name: 班级名称
- students: 所有学生列表
selected_students: 被选中的学生列表
- mode: 点名模式
- count: 点名人数
- random_method: 随机方法
- probability_weight_method: 概率权重方法
-
+ students_dict_list: 完整的学生列表,用于计算权重
+ group_filter: 小组过滤器,指定本次抽取的小组范围,None表示不限制
+ gender_filter: 性别过滤器,指定本次抽取的性别范围,None表示不限制
+
Returns:
bool: 保存是否成功
"""
try:
# 获取当前时间
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
-
+
# 加载现有历史记录
history_data = load_history_data("roll_call", class_name)
-
- # 更新元数据
- if "metadata" not in history_data:
- history_data["metadata"] = {}
- history_data["metadata"]["last_updated"] = current_time
-
- # 更新学生数据
+
+ # 初始化数据结构
if "students" not in history_data:
history_data["students"] = {}
-
- # 更新所有学生的信息
- for student in students:
+ if "group_stats" not in history_data:
+ history_data["group_stats"] = {}
+ if "gender_stats" not in history_data:
+ history_data["gender_stats"] = {}
+ if "total_rounds" not in history_data:
+ history_data["total_rounds"] = 0
+ if "total_stats" not in history_data:
+ history_data["total_stats"] = 0
+
+ # 获取被选中的学生名称列表
+ selected_names = [s.get("name", "") for s in selected_students]
+
+ # 计算权重
+ students_dict_list = get_student_list(class_name)
+ students_with_weight = calculate_weight(students_dict_list, class_name)
+
+ # 更新每个被选中学生的历史记录
+ for student in selected_students:
student_name = student.get("name", "")
if not student_name:
continue
-
+
+ # 如果学生不存在于历史记录中,创建新记录
if student_name not in history_data["students"]:
history_data["students"][student_name] = {
- "id": student.get("id", 0),
- "gender": student.get("gender", "未知"),
- "group": student.get("group", "未分组"),
"total_count": 0,
"group_gender_count": 0,
"last_drawn_time": "",
"rounds_missed": 0,
- "history": []
+ "history": [],
}
-
- # 如果学生被选中,更新其信息
- is_selected = any(s.get("name") == student_name for s in selected_students)
- if is_selected:
- history_data["students"][student_name]["total_count"] += 1
- history_data["students"][student_name]["last_drawn_time"] = current_time
- history_data["students"][student_name]["history"].append(current_time)
-
+
+ # 更新学生的基本信息
+ student_data = history_data["students"][student_name]
+ student_data["total_count"] += 1
+ student_data["last_drawn_time"] = current_time
+ student_data["rounds_missed"] = 0 # 重置未选中次数
+
+ draw_method = 1
+
+ # 获取当前被选中学生的权重信息
+ current_student_weight = None
+ for student_with_weight in students_with_weight:
+ if student_with_weight.get("name") == student_name:
+ current_student_weight = student_with_weight.get("next_weight", 0)
+ break
+
+ history_entry = {
+ "draw_method": draw_method,
+ "draw_time": current_time,
+ "draw_people_numbers": len(selected_students),
+ "draw_group": group_filter,
+ "draw_gender": gender_filter,
+ "weight": current_student_weight,
+ }
+ student_data["history"].append(history_entry)
+
# 更新未被选中的学生的未选中次数
- selected_names = [s.get("name") for s in selected_students]
for student_name, student_data in history_data["students"].items():
if student_name not in selected_names:
student_data["rounds_missed"] += 1
-
- # 添加会话记录
- if "sessions" not in history_data:
- history_data["sessions"] = []
-
- session_record = {
- "draw_time": current_time,
- "mode": mode,
- "count": count,
- "random_method": random_method,
- "probability_weight_method": probability_weight_method,
- "selected_students": [
- {
- "name": s.get("name", ""),
- "id": s.get("id", 0),
- "gender": s.get("gender", "未知"),
- "group": s.get("group", "未分组")
- } for s in selected_students
- ]
- }
- history_data["sessions"].append(session_record)
-
- # 添加个人统计记录
- if "individual_stats" not in history_data:
- history_data["individual_stats"] = []
-
- # 统计性别和小组
- gender_count = {"男": 0, "女": 0, "未知": 0}
- group_count = {}
+
+ # 更新小组和性别统计
for student in selected_students:
- gender = student.get("gender", "未知")
- group = student.get("group", "未分组")
-
- gender_count[gender] = gender_count.get(gender, 0) + 1
- group_count[group] = group_count.get(group, 0) + 1
-
- individual_stat = {
- "time": current_time,
- "mode": mode,
- "people_count": len(selected_students),
- "gender": f"男:{gender_count.get('男', 0)} 女:{gender_count.get('女', 0)}",
- "group": ", ".join([f"{group}:{count}" for group, count in group_count.items()])
- }
- history_data["individual_stats"].append(individual_stat)
-
+ group = student.get("group", "")
+ gender = student.get("gender", "")
+
+ # 更新小组统计
+ if group not in history_data["group_stats"]:
+ history_data["group_stats"][group] = 0
+ history_data["group_stats"][group] += 1
+
+ # 更新性别统计
+ if gender not in history_data["gender_stats"]:
+ history_data["gender_stats"][gender] = 0
+ history_data["gender_stats"][gender] += 1
+
+ # 更新总轮数和总统计数
+ history_data["total_rounds"] += 1
+ history_data["total_stats"] += len(selected_students)
+
# 保存历史记录
return save_history_data("roll_call", class_name, history_data)
-
+
except Exception as e:
logger.error(f"保存点名历史记录失败: {e}")
return False
+
+# ==================================================
+# 辅助函数
+# ==================================================
def get_all_names(history_type: str, list_name: str) -> list:
"""获取历史记录中所有名称
-
+
Args:
history_type: 历史记录类型
list_name: 列表名称
-
+
Returns:
list: 所有名称列表
"""
try:
if history_type == "roll_call":
list_name_data = get_student_list(list_name)
- return [item["name"] for item in list_name_data if "name" in item]
+ return [
+ item["name"]
+ for item in list_name_data
+ if "name" in item and item["name"]
+ ]
else:
- list_name_data = load_history_data(history_type, list_name)
- return list(list_name_data.get('lotterys', {}).keys())
+ list_name_data = get_pool_list(list_name)
+ return [
+ item["name"]
+ for item in list_name_data
+ if "name" in item and item["name"]
+ ]
except Exception as e:
logger.error(f"获取历史记录中所有名称失败: {e}")
return []
+
+
+def format_table_item(
+ value: Union[str, int, float], is_percentage: bool = False
+) -> str:
+ """格式化表格项显示值
+
+ Args:
+ value: 要格式化的值
+ is_percentage: 是否为百分比值
+
+ Returns:
+ str: 格式化后的字符串
+ """
+ if isinstance(value, (int, float)):
+ if is_percentage:
+ return f"{value:.2%}"
+ else:
+ return f"{value:.2f}"
+ return str(value)
+
+
+def create_table_item(
+ value: Union[str, int, float],
+ font_size: int = 12,
+ is_centered: bool = True,
+ is_percentage: bool = False,
+) -> "QTableWidgetItem":
+ """创建表格项
+
+ Args:
+ value: 要显示的值
+ font_size: 字体大小
+ is_centered: 是否居中
+ is_percentage: 是否为百分比值
+
+ Returns:
+ QTableWidgetItem: 表格项对象
+ """
+ display_value = format_table_item(value, is_percentage)
+ item = QTableWidgetItem(display_value)
+ from app.tools.personalised import load_custom_font
+
+ item.setFont(QFont(load_custom_font(), font_size))
+ if is_centered:
+ item.setTextAlignment(
+ Qt.AlignmentFlag.AlignCenter | Qt.AlignmentFlag.AlignVCenter
+ )
+ item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsEditable)
+ return item
diff --git a/app/tools/language_manager.py b/app/tools/language_manager.py
index bc63e640..15d8e8fd 100644
--- a/app/tools/language_manager.py
+++ b/app/tools/language_manager.py
@@ -8,73 +8,110 @@
from app.tools.path_utils import get_path, get_resources_path
from app.tools.settings_access import readme_settings
+
# from app.Language.ZH_CN import ZH_CN
import glob
+import importlib
import importlib.util
+import pkgutil
from app.tools.variable import LANGUAGE_MODULE_DIR
+
# ==================================================
# 简化的语言管理器类
# ==================================================
class SimpleLanguageManager:
"""负责获取当前语言和全部语言"""
-
+
def __init__(self):
self._current_language: Optional[str] = None
-
+
# 默认加载中文,从模块文件动态生成
merged_zh_cn = self._merge_language_files("ZH_CN")
- self._loaded_languages: Dict[str, Dict[str, Any]] = {
- "ZH_CN": merged_zh_cn
- }
-
+ self._loaded_languages: Dict[str, Dict[str, Any]] = {"ZH_CN": merged_zh_cn}
+
# 加载resources/Language文件夹下的所有语言文件
self._load_all_languages()
-
def _merge_language_files(self, language_code: Optional[str]) -> Dict[str, Any]:
"""
从模块化语言文件中合并生成完整的语言字典
-
+
Args:
language_code: 语言代码,默认为"ZH_CN"
-
+
Returns:
合并后的语言字典
"""
merged = {}
language_code = "ZH_CN" if not language_code else language_code
language_dir = get_path(LANGUAGE_MODULE_DIR)
-
- # 检查语言目录是否存在
- if not os.path.exists(language_dir):
+
+ module_entries: List[tuple[str, Optional[str]]] = []
+
+ if os.path.isdir(language_dir):
+ # 开发环境:直接从文件系统查找
+ language_module_files = glob.glob(os.path.join(language_dir, "*.py"))
+ for file_path in language_module_files:
+ if file_path.endswith("__init__.py"):
+ continue
+ module_entries.append(
+ (os.path.splitext(os.path.basename(file_path))[0], file_path)
+ )
+ else:
+ # 打包环境:利用包信息进行枚举
logger.warning(f"语言模块目录不存在: {language_dir}")
+ try:
+ language_package = importlib.import_module("app.Language.modules")
+ discovered = {
+ name.rsplit(".", 1)[-1]
+ for _, name, is_pkg in pkgutil.walk_packages(
+ getattr(language_package, "__path__", []),
+ language_package.__name__ + ".",
+ )
+ if not is_pkg and not name.endswith(".__init__")
+ }
+ if discovered:
+ module_entries.extend(
+ (module_name, None) for module_name in sorted(discovered)
+ )
+ else:
+ logger.warning("未能通过 pkgutil.walk_packages 发现语言模块")
+ except Exception as discovery_error:
+ logger.error(f"枚举语言模块失败: {discovery_error}")
+
+ if not module_entries:
+ logger.warning("未找到任何语言模块,返回空语言数据")
return merged
-
- # 获取所有Python模块文件
- language_module_files = glob.glob(os.path.join(language_dir, "*.py"))
- language_module_files = [f for f in language_module_files if not f.endswith("__init__.py")]
-
- # 遍历所有模块文件并动态导入
- for file_path in language_module_files:
+
+ # 遍历所有模块并导入
+ for module_name, file_path in module_entries:
try:
- # 从文件名获取模块名(去掉.py扩展名)
- language_module_name = os.path.basename(file_path)[:-3]
-
- # 动态导入模块
- spec = importlib.util.spec_from_file_location(language_module_name, file_path)
- if spec is None:
- logger.warning(f"无法创建模块规范: {file_path}")
- continue
-
- module = importlib.util.module_from_spec(spec)
- if spec.loader is None:
- logger.warning(f"模块加载器为空: {file_path}")
- continue
-
- spec.loader.exec_module(module)
-
+ # 优先使用标准导入(适用于打包环境)
+ try:
+ module = __import__(
+ f"app.Language.modules.{module_name}",
+ fromlist=[module_name],
+ )
+ except ImportError:
+ if not file_path:
+ raise
+ # 如果直接导入失败且存在文件路径,使用动态加载(开发环境)
+ spec = importlib.util.spec_from_file_location(
+ module_name, file_path
+ )
+ if spec is None:
+ logger.warning(f"无法创建模块规范: {file_path}")
+ continue
+
+ module = importlib.util.module_from_spec(spec)
+ if spec.loader is None:
+ logger.warning(f"模块加载器为空: {file_path}")
+ continue
+
+ spec.loader.exec_module(module)
+
# 遍历模块中的所有属性
for attr_name in dir(module):
attr_value = getattr(module, attr_name)
@@ -82,11 +119,11 @@ def _merge_language_files(self, language_code: Optional[str]) -> Dict[str, Any]:
if isinstance(attr_value, dict) and language_code in attr_value:
# 将模块内容合并到结果字典中
merged[attr_name] = attr_value[language_code]
-
+
except Exception as e:
logger.error(f"导入语言模块 {file_path} 时出错: {e}")
continue
-
+
return merged
def _load_all_languages(self) -> None:
@@ -94,35 +131,35 @@ def _load_all_languages(self) -> None:
try:
# 获取语言文件夹路径
language_dir = get_resources_path("Language")
-
+
if not language_dir or not os.path.exists(language_dir):
return
-
+
# 遍历文件夹中的所有.json文件
for filename in os.listdir(language_dir):
- if filename.endswith('.json'):
+ if filename.endswith(".json"):
language_code = filename[:-5] # 去掉.json后缀
-
+
# 跳过已加载的语言
if language_code in self._loaded_languages:
continue
-
+
file_path = os.path.join(language_dir, filename)
-
+
try:
# 加载语言文件
- with open(file_path, 'r', encoding='utf-8') as f:
+ with open(file_path, "r", encoding="utf-8") as f:
language_data = json.load(f)
self._loaded_languages[language_code] = language_data
except Exception as e:
logger.error(f"加载语言文件 {filename} 时出错: {e}")
-
+
except Exception as e:
logger.error(f"加载语言文件夹时出错: {e}")
-
+
def get_current_language(self) -> str:
"""获取当前语言代码
-
+
Returns:
当前语言代码
"""
@@ -131,51 +168,53 @@ def get_current_language(self) -> str:
self._current_language = readme_settings("basic_settings", "language")
if self._current_language is None:
self._current_language = "ZH_CN"
-
+
return self._current_language
-
+
def get_current_language_data(self) -> Dict[str, Any]:
"""获取当前语言数据
-
+
Returns:
当前语言数据字典
"""
language_code = self.get_current_language()
-
+
# 如果语言未加载,返回默认中文
if language_code not in self._loaded_languages:
return self._loaded_languages["ZH_CN"]
-
+
return self._loaded_languages[language_code]
-
+
def get_all_languages(self) -> Dict[str, Dict[str, Any]]:
"""获取所有已加载的语言数据
-
+
Returns:
包含所有语言数据的字典,键为语言代码,值为语言数据字典
"""
return dict(self._loaded_languages)
-
+
def get_language_info(self, language_code: str) -> Optional[Dict[str, Any]]:
"""获取指定语言的信息(translate_JSON_file字段)
-
+
Args:
language_code: 语言代码
-
+
Returns:
语言信息字典,如果语言不存在则返回None
"""
if language_code not in self._loaded_languages:
return None
-
+
language_data = self._loaded_languages[language_code]
-
+
# 返回translate_JSON_file字段,如果不存在则返回空字典
return language_data.get("translate_JSON_file", {})
+
# 创建全局语言管理器实例
_simple_language_manager = None
+
def get_simple_language_manager() -> SimpleLanguageManager:
"""获取全局简化语言管理器实例"""
global _simple_language_manager
@@ -183,28 +222,31 @@ def get_simple_language_manager() -> SimpleLanguageManager:
_simple_language_manager = SimpleLanguageManager()
return _simple_language_manager
+
# ==================================================
# 简化的语言管理辅助函数
# ==================================================
def get_current_language() -> str:
"""获取当前语言代码
-
+
Returns:
当前语言代码
"""
return get_simple_language_manager().get_current_language()
+
def get_all_languages() -> Dict[str, Dict[str, Any]]:
"""获取所有已加载的语言数据
-
+
Returns:
包含所有语言数据的字典,键为语言代码,值为语言数据字典
"""
return get_simple_language_manager().get_all_languages()
+
def get_all_languages_name() -> List[str]:
"""获取所有已加载的语言名称
-
+
Returns:
包含所有语言名称的列表,每个元素为语言名称
"""
@@ -215,21 +257,23 @@ def get_all_languages_name() -> List[str]:
language_names.append(name)
return language_names
+
def get_current_language_data() -> Dict[str, Any]:
"""获取当前语言数据
-
+
Returns:
当前语言数据字典
"""
return get_simple_language_manager().get_current_language_data()
+
def get_language_info(language_code: str) -> Optional[Dict[str, Any]]:
"""获取指定语言的信息(translate_JSON_file字段)
-
+
Args:
language_code: 语言代码
-
+
Returns:
语言信息字典,如果语言不存在则返回None
"""
- return get_simple_language_manager().get_language_info(language_code)
\ No newline at end of file
+ return get_simple_language_manager().get_language_info(language_code)
diff --git a/app/tools/list.py b/app/tools/list.py
index 514670cf..27aeac76 100644
--- a/app/tools/list.py
+++ b/app/tools/list.py
@@ -1,62 +1,62 @@
# ==================================================
# 导入模块
# ==================================================
-import os
import json
-from pathlib import Path
-from typing import List, Dict, Any
+from typing import List, Dict, Any, Tuple
from loguru import logger
from app.tools.path_utils import *
+
# ==================================================
# 班级列表管理函数
# ==================================================
def get_class_name_list() -> List[str]:
"""获取班级名称列表
-
+
从 app/resources/list/roll_call_list 文件夹中读取所有班级名单文件,
并返回班级名称列表
-
+
Returns:
List[str]: 班级名称列表
"""
try:
# 获取班级名单文件夹路径
roll_call_list_dir = get_path("app/resources/list/roll_call_list")
-
+
# 如果文件夹不存在,创建文件夹
if not roll_call_list_dir.exists():
logger.warning(f"班级名单文件夹不存在: {roll_call_list_dir}")
roll_call_list_dir.mkdir(parents=True, exist_ok=True)
return []
-
+
# 获取文件夹中的所有文件
class_files = []
for file_path in roll_call_list_dir.glob("*.json"):
# 获取文件名(不带扩展名)作为班级名称
class_name = file_path.stem
class_files.append(class_name)
-
+
# 按字母顺序排序
class_files.sort()
-
+
# logger.debug(f"找到 {len(class_files)} 个班级: {class_files}")
return class_files
-
+
except Exception as e:
logger.error(f"获取班级列表失败: {e}")
return []
+
def get_student_list(class_name: str) -> List[Dict[str, Any]]:
"""获取指定班级的学生列表
-
+
从 app/resources/list/roll_call_list 文件夹中读取指定班级的名单文件,
并返回学生列表
-
+
Args:
class_name: 班级名称
-
+
Returns:
List[Dict[str, Any]]: 学生列表,每个学生是一个字典,包含姓名、ID、性别、小组等信息
"""
@@ -64,16 +64,16 @@ def get_student_list(class_name: str) -> List[Dict[str, Any]]:
# 获取班级名单文件路径
roll_call_list_dir = get_path("app/resources/list/roll_call_list")
class_file_path = roll_call_list_dir / f"{class_name}.json"
-
+
# 如果文件不存在,返回空列表
if not class_file_path.exists():
logger.warning(f"班级名单文件不存在: {class_file_path}")
return []
-
+
# 读取JSON文件
- with open(class_file_path, 'r', encoding='utf-8') as f:
+ with open(class_file_path, "r", encoding="utf-8") as f:
student_data = json.load(f)
-
+
# 将字典数据转换为列表形式
student_list = []
for name, info in student_data.items():
@@ -82,68 +82,141 @@ def get_student_list(class_name: str) -> List[Dict[str, Any]]:
"id": info.get("id", 0),
"gender": info.get("gender", "未知"),
"group": info.get("group", "未分组"),
- "exist": info.get("exist", True)
+ "exist": info.get("exist", True),
}
student_list.append(student)
-
+
# 按ID排序
student_list.sort(key=lambda x: x["id"])
-
+
# logger.debug(f"班级 {class_name} 共有 {len(student_list)} 名学生")
return student_list
-
+
except Exception as e:
logger.error(f"获取学生列表失败: {e}")
return []
+
+def get_group_list(class_name: str) -> List[Dict[str, Any]]:
+ """获取指定班级的小组列表
+
+ 从 app/resources/list/roll_call_list 文件夹中读取指定班级的名单文件,
+ 并返回小组列表
+
+ Args:
+ class_name: 班级名称
+
+ Returns:
+ List[Dict[str, Any]]: 小组列表,每个小组是一个字典,包含小组名称、学生列表等信息
+ """
+ student_list = get_student_list(class_name)
+ group_set = set() # 使用集合确保不重复
+ for student in student_list:
+ group_name = student["group"]
+ group_set.add(group_name)
+
+ # 转换为列表并排序
+ group_list = sorted(list(group_set))
+ return group_list
+
+
+def get_gender_list(class_name: str) -> List[str]:
+ """获取指定班级的性别列表
+
+ 从 app/resources/list/roll_call_list 文件夹中读取指定班级的名单文件,
+ 并返回性别列表
+
+ Args:
+ class_name: 班级名称
+
+ Returns:
+ List[str]: 性别列表,包含所有学生的性别
+ """
+ student_list = get_student_list(class_name)
+ gender_set = set() # 使用集合确保不重复
+ for student in student_list:
+ gender = student["gender"]
+ gender_set.add(gender)
+
+ # 转换为列表并排序
+ gender_list = sorted(list(gender_set))
+ return gender_list
+
+
+def get_group_members(class_name: str, group_name: str) -> List[Dict[str, Any]]:
+ """获取指定班级中指定小组的成员列表
+
+ 从 app/resources/list/roll_call_list 文件夹中读取指定班级的名单文件,
+ 并返回指定小组的成员列表
+
+ Args:
+ class_name: 班级名称
+ group_name: 小组名称
+
+ Returns:
+ List[Dict[str, Any]]: 小组成员列表,每个成员是一个字典,包含姓名、ID、性别、小组等信息
+ """
+ student_list = get_student_list(class_name)
+ group_members = []
+
+ for student in student_list:
+ if student["group"] == group_name:
+ group_members.append(student)
+
+ # 按ID排序
+ group_members.sort(key=lambda x: x["id"])
+ return group_members
+
+
# ==================================================
# 奖池列表管理函数
# ==================================================
def get_pool_name_list() -> List[str]:
"""获取奖池名称列表
-
+
从 app/resources/list/lottery_list 文件夹中读取所有奖池名单文件,
并返回奖池名称列表
-
+
Returns:
List[str]: 奖池名称列表
"""
try:
# 获取奖池名单文件夹路径
lottery_list_dir = get_path("app/resources/list/lottery_list")
-
+
# 如果文件夹不存在,创建文件夹
if not lottery_list_dir.exists():
logger.warning(f"奖池名单文件夹不存在: {lottery_list_dir}")
lottery_list_dir.mkdir(parents=True, exist_ok=True)
return []
-
+
# 获取文件夹中的所有文件
pool_files = []
for file_path in lottery_list_dir.glob("*.json"):
# 获取文件名(不带扩展名)作为奖池名称
pool_name = file_path.stem
pool_files.append(pool_name)
-
+
# 按字母顺序排序
pool_files.sort()
-
+
# logger.debug(f"找到 {len(pool_files)} 个奖池: {pool_files}")
return pool_files
-
+
except Exception as e:
logger.error(f"获取奖池列表失败: {e}")
return []
+
def get_pool_data(pool_name: str) -> Dict[str, Any]:
"""获取指定奖池的数据
-
+
从 app/resources/list/lottery_list 文件夹中读取指定奖池的名单文件,
并返回奖池数据
-
+
Args:
pool_name: 奖池名称
-
+
Returns:
Dict[str, Any]: 奖池数据,包含奖品名称、权重、是否存在等信息
"""
@@ -151,16 +224,16 @@ def get_pool_data(pool_name: str) -> Dict[str, Any]:
# 获取奖池名单文件路径
lottery_list_dir = get_path("app/resources/list/lottery_list")
pool_file_path = lottery_list_dir / f"{pool_name}.json"
-
+
# 如果文件不存在,返回空字典
if not pool_file_path.exists():
logger.warning(f"奖池名单文件不存在: {pool_file_path}")
return {}
-
+
# 读取JSON文件
- with open(pool_file_path, 'r', encoding='utf-8') as f:
+ with open(pool_file_path, "r", encoding="utf-8") as f:
pool_data = json.load(f)
-
+
# 将字典数据转换为列表形式
pool_list = []
for name, info in pool_data.items():
@@ -168,29 +241,30 @@ def get_pool_data(pool_name: str) -> Dict[str, Any]:
"name": name,
"id": info.get("id", 0),
"weight": info.get("weight", 1),
- "exist": info.get("exist", True)
+ "exist": info.get("exist", True),
}
pool_list.append(pool)
-
+
# 按ID排序
pool_list.sort(key=lambda x: x["id"])
-
+
# logger.debug(f"奖池 {pool_name} 共有 {len(pool_list)} 个奖品")
return pool_list
-
+
except Exception as e:
logger.error(f"获取奖池数据失败: {e}")
return []
+
def get_pool_list(pool_name: str) -> List[Dict[str, Any]]:
"""获取指定奖池的奖品列表
-
+
从 app/resources/list/lottery_list 文件夹中读取指定奖池的名单文件,
并返回奖品列表
-
+
Args:
pool_name: 奖池名称
-
+
Returns:
List[Dict[str, Any]]: 奖品列表,每个奖品是一个字典,包含名称、ID、权重等信息
"""
@@ -198,16 +272,16 @@ def get_pool_list(pool_name: str) -> List[Dict[str, Any]]:
# 获取奖池名单文件路径
lottery_list_dir = get_path("app/resources/list/lottery_list")
pool_file_path = lottery_list_dir / f"{pool_name}.json"
-
+
# 如果文件不存在,返回空列表
if not pool_file_path.exists():
logger.warning(f"奖池名单文件不存在: {pool_file_path}")
return []
-
+
# 读取JSON文件
- with open(pool_file_path, 'r', encoding='utf-8') as f:
+ with open(pool_file_path, "r", encoding="utf-8") as f:
pool_data = json.load(f)
-
+
# 将字典数据转换为列表形式
pool_list = []
for name, info in pool_data.items():
@@ -215,121 +289,304 @@ def get_pool_list(pool_name: str) -> List[Dict[str, Any]]:
"name": name,
"id": info.get("id", 0),
"weight": info.get("weight", 1),
- "exist": info.get("exist", True)
+ "exist": info.get("exist", True),
}
pool_list.append(pool)
-
+
# 按ID排序
pool_list.sort(key=lambda x: x["id"])
-
+
# logger.debug(f"奖池 {pool_name} 共有 {len(pool_list)} 个奖品")
return pool_list
-
+
except Exception as e:
logger.error(f"获取奖池列表失败: {e}")
return []
+
# ==================================================
# 学生数据处理函数
# ==================================================
-def filter_students_data(data: Dict[str, Any], group_index: int, gender_index: int) -> List[Dict[str, Any]]:
+def filter_students_data(
+ data: Dict[str, Any],
+ group_index: int,
+ group_filter: str,
+ gender_index: int,
+ gender_filter: str,
+) -> List[Dict[str, Any]]:
"""根据小组和性别条件过滤学生数据
-
+
根据指定的小组和性别索引条件过滤学生数据,返回包含完整学生信息的列表
-
+
Args:
data: 学生数据字典,键为学生姓名,值为包含学生信息的字典
- group_index: 小组筛选索引,0表示抽取全班学生,1表示抽取小组组号,大于1表示具体的小组索引
+ group_index: 小组筛选索引,0表示抽取全班学生,1表示抽取小组组号,大于等于2表示具体的小组索引
+ group_filter: 小组筛选条件,当group_index>=2时使用
gender_index: 性别筛选索引,0表示抽取所有性别,1表示男性,2表示女性
-
+ gender_filter: 性别筛选条件,"男"或"女"
+
Returns:
List[Tuple]: 包含(id, name, gender, group, exist)的元组列表
"""
students_data = []
-
+
try:
- # 获取所有小组列表
- groups = set()
- for student_name, student_info in data.items():
- if isinstance(student_info, dict) and 'id' in student_info:
- group = student_info.get('group', '')
- if group: # 只添加非空小组
- groups.add(group)
- sorted_groups = sorted(list(groups), key=lambda x: str(x))
-
# 处理全班学生抽取 (group_index = 0)
if group_index == 0:
if gender_index == 0: # 抽取所有性别
for student_name, student_info in data.items():
- if isinstance(student_info, dict) and 'id' in student_info:
- id = student_info.get('id', '')
- name = student_name.replace('【', '').replace('】', '')
- gender = student_info.get('gender', '')
- group = student_info.get('group', '')
- exist = student_info.get('exist', True)
+ if isinstance(student_info, dict) and "id" in student_info:
+ id = student_info.get("id", "")
+ name = student_name
+ gender = student_info.get("gender", "")
+ group = student_info.get("group", "")
+ exist = student_info.get("exist", True)
students_data.append((id, name, gender, group, exist))
else: # 抽取特定性别
- gender_value = '男' if gender_index == 1 else '女'
for student_name, student_info in data.items():
- if isinstance(student_info, dict) and 'id' in student_info:
- id = student_info.get('id', '')
- name = student_name.replace('【', '').replace('】', '')
- gender = student_info.get('gender', '')
- group = student_info.get('group', '')
- exist = student_info.get('exist', True)
- if gender == gender_value:
+ if isinstance(student_info, dict) and "id" in student_info:
+ id = student_info.get("id", "")
+ name = student_name
+ gender = student_info.get("gender", "")
+ group = student_info.get("group", "")
+ exist = student_info.get("exist", True)
+ if gender == gender_filter:
students_data.append((id, name, gender, group, exist))
-
+
# 处理小组组号抽取 (group_index = 1)
elif group_index == 1:
groups_set = set()
for student_name, student_info in data.items():
- if isinstance(student_info, dict) and 'id' in student_info:
- id = student_info.get('id', '')
- name = student_name.replace('【', '').replace('】', '')
- gender = student_info.get('gender', '')
- group = student_info.get('group', '')
- exist = student_info.get('exist', True)
+ if isinstance(student_info, dict) and "id" in student_info:
+ group = student_info.get("group", "")
+ gender = student_info.get("gender", "")
+ exist = student_info.get("exist", True)
if group: # 只添加非空小组
- groups_set.add((id, name, gender, group, exist))
- students_data.append((id, name, gender, group, exist))
+ if (
+ gender_index == 0 or gender == gender_filter
+ ): # 根据性别条件过滤
+ groups_set.add(group)
- # 对小组进行排序
- students_data = sorted(list(groups_set), key=lambda x: str(x))
-
- # 处理指定小组抽取 (group_index > 1)
- else:
- # 获取指定的小组名称
- if 0 < group_index - 2 < len(sorted_groups):
- group_name = sorted_groups[group_index - 2]
-
- if gender_index == 0: # 抽取所有性别
- for student_name, student_info in data.items():
- if isinstance(student_info, dict) and 'id' in student_info:
- id = student_info.get('id', '')
- name = student_name.replace('【', '').replace('】', '')
- gender = student_info.get('gender', '')
- group = student_info.get('group', '')
- exist = student_info.get('exist', True)
- if group == group_name:
- students_data.append((id, name, gender, group, exist))
- else: # 抽取特定性别
- gender_value = '男' if gender_index == 1 else '女'
- for student_name, student_info in data.items():
- if isinstance(student_info, dict) and 'id' in student_info:
- id = student_info.get('id', '')
- name = student_name.replace('【', '').replace('】', '')
- gender = student_info.get('gender', '')
- group = student_info.get('group', '')
- exist = student_info.get('exist', True)
- if gender == gender_value and group == group_name:
- students_data.append((id, name, gender, group, exist))
+ # 对小组进行排序,按小组名称排序
+ # 返回格式为 (id, name, gender, group, exist),但小组模式下只需要小组名称
+ students_data = []
+ for group_name in sorted(groups_set):
+ # 在小组模式下,我们只需要小组名称,其他字段可以留空或使用默认值
+ students_data.append((None, group_name, None, group_name, True))
+
+ # 处理指定小组抽取 (group_index >= 2)
+ elif group_index >= 2:
+ for student_name, student_info in data.items():
+ if isinstance(student_info, dict) and "id" in student_info:
+ id = student_info.get("id", "")
+ name = student_name
+ gender = student_info.get("gender", "")
+ group = student_info.get("group", "")
+ exist = student_info.get("exist", True)
+ if group == group_filter: # 匹配指定小组
+ if (
+ gender_index == 0 or gender == gender_filter
+ ): # 根据性别条件过滤
+ students_data.append((id, name, gender, group, exist))
# 过滤学生信息的exist为False的学生
students_data = list(filter(lambda x: x[4], students_data))
-
+
return students_data
-
+
except Exception as e:
logger.error(f"过滤学生数据失败: {e}")
- return []
\ No newline at end of file
+ return []
+
+
+# ==================================================
+# 学生数据导出函数
+# ==================================================
+def export_student_data(
+ class_name: str, file_path: str, export_format: str
+) -> Tuple[bool, str]:
+ """导出学生数据到指定文件
+
+ 从 app/resources/list/roll_call_list 文件夹中读取指定班级的名单文件,
+ 并根据指定格式导出到文件
+
+ Args:
+ class_name: 班级名称
+ file_path: 导出文件路径
+ export_format: 导出格式 ('excel', 'csv', 'txt')
+
+ Returns:
+ Tuple[bool, str]: (是否成功, 成功/错误消息)
+ """
+ try:
+ # 获取班级名单文件路径
+ roll_call_list_dir = get_path("app/resources/list/roll_call_list")
+ class_file_path = roll_call_list_dir / f"{class_name}.json"
+
+ # 如果文件不存在,返回错误
+ if not class_file_path.exists():
+ error_msg = f"班级文件 '{class_name}.json' 不存在"
+ logger.error(error_msg)
+ return False, error_msg
+
+ # 读取JSON文件
+ with open(class_file_path, "r", encoding="utf-8") as f:
+ data = json.load(f)
+
+ if not data:
+ error_msg = "当前班级没有学生数据"
+ logger.warning(error_msg)
+ return False, error_msg
+
+ # 根据导出格式处理数据
+ if export_format.lower() == "excel":
+ return _export_to_excel(data, file_path)
+ elif export_format.lower() == "csv":
+ return _export_to_csv(data, file_path)
+ elif export_format.lower() == "txt":
+ return _export_to_txt(data, file_path)
+ else:
+ error_msg = f"不支持的导出格式: {export_format}"
+ logger.error(error_msg)
+ return False, error_msg
+
+ except FileNotFoundError:
+ error_msg = f"班级文件 '{class_name}.json' 不存在"
+ logger.error(error_msg)
+ return False, error_msg
+ except json.JSONDecodeError:
+ error_msg = f"班级文件 '{class_name}.json' 格式错误"
+ logger.error(error_msg)
+ return False, error_msg
+ except Exception as e:
+ error_msg = f"导出学生名单时出错: {str(e)}"
+ logger.error(error_msg)
+ return False, error_msg
+
+
+def _export_to_excel(data: Dict[str, Any], file_path: str) -> Tuple[bool, str]:
+ """导出学生数据为Excel文件
+
+ Args:
+ data: 学生数据字典
+ file_path: 导出文件路径
+
+ Returns:
+ Tuple[bool, str]: (是否成功, 成功/错误消息)
+ """
+ try:
+ # 转换为DataFrame
+ export_data = []
+ for name, info in data.items():
+ export_data.append(
+ {
+ "学号": info["id"],
+ "姓名": name,
+ "性别": info["gender"],
+ "所处小组": info["group"],
+ }
+ )
+
+ # 延迟导入 pandas,避免程序启动时加载大型 C 扩展
+ try:
+ import pandas as pd
+ except Exception as e:
+ logger.error(f"导出Excel需要 pandas 库,但导入失败: {e}")
+ return False, "导出失败: pandas 未安装或导入错误"
+
+ df = pd.DataFrame(export_data)
+
+ # 确保文件扩展名正确
+ if not file_path.endswith(".xlsx"):
+ file_path += ".xlsx"
+
+ # 保存为xlsx文件
+ df.to_excel(file_path, index=False, engine="openpyxl")
+
+ success_msg = f"学生名单已导出到: {file_path}"
+ logger.info(success_msg)
+ return True, success_msg
+
+ except Exception as e:
+ error_msg = f"导出Excel文件时出错: {str(e)}"
+ logger.error(error_msg)
+ return False, error_msg
+
+
+def _export_to_csv(data: Dict[str, Any], file_path: str) -> Tuple[bool, str]:
+ """导出学生数据为CSV文件
+
+ Args:
+ data: 学生数据字典
+ file_path: 导出文件路径
+
+ Returns:
+ Tuple[bool, str]: (是否成功, 成功/错误消息)
+ """
+ try:
+ # 转换为DataFrame
+ export_data = []
+ for name, info in data.items():
+ export_data.append(
+ {
+ "学号": info["id"],
+ "姓名": name,
+ "性别": info["gender"],
+ "所处小组": info["group"],
+ }
+ )
+
+ # 延迟导入 pandas,避免程序启动时加载大型 C 扩展
+ try:
+ import pandas as pd
+ except Exception as e:
+ logger.error(f"导出CSV需要 pandas 库,但导入失败: {e}")
+ return False, "导出失败: pandas 未安装或导入错误"
+
+ df = pd.DataFrame(export_data)
+
+ # 确保文件扩展名正确
+ if not file_path.endswith(".csv"):
+ file_path += ".csv"
+
+ # 保存为CSV文件
+ df.to_csv(file_path, index=False, encoding="utf-8-sig")
+
+ success_msg = f"学生名单已导出到: {file_path}"
+ logger.info(success_msg)
+ return True, success_msg
+
+ except Exception as e:
+ error_msg = f"导出CSV文件时出错: {str(e)}"
+ logger.error(error_msg)
+ return False, error_msg
+
+
+def _export_to_txt(data: Dict[str, Any], file_path: str) -> Tuple[bool, str]:
+ """导出学生数据为TXT文件(仅姓名)
+
+ Args:
+ data: 学生数据字典
+ file_path: 导出文件路径
+
+ Returns:
+ Tuple[bool, str]: (是否成功, 成功/错误消息)
+ """
+ try:
+ # 确保文件扩展名正确
+ if not file_path.endswith(".txt"):
+ file_path += ".txt"
+
+ # 提取姓名并保存为TXT文件,每行一个姓名
+ with open(file_path, "w", encoding="utf-8") as f:
+ for name in data.keys():
+ f.write(f"{name}\n")
+
+ success_msg = f"学生名单已导出到: {file_path}"
+ logger.info(success_msg)
+ return True, success_msg
+
+ except Exception as e:
+ error_msg = f"导出TXT文件时出错: {str(e)}"
+ logger.error(error_msg)
+ return False, error_msg
diff --git a/app/tools/path_utils.py b/app/tools/path_utils.py
index 46cb7a30..bdfbc1ef 100644
--- a/app/tools/path_utils.py
+++ b/app/tools/path_utils.py
@@ -19,67 +19,72 @@
# ==================================================
# 导入模块
# ==================================================
-import os
import sys
from pathlib import Path
-from typing import Union, Optional
+from typing import Union
from loguru import logger
from app.tools.variable import *
+
# ==================================================
# 路径管理器类
# ==================================================
class PathManager:
"""路径管理器 - 统一管理应用程序中的所有路径"""
-
+
def __init__(self):
"""初始化路径管理器"""
self._app_root = self._get_app_root()
logger.debug(f"应用程序根目录: {self._app_root}")
-
+
def _get_app_root(self) -> Path:
"""获取应用程序根目录
-
+
Returns:
Path: 应用程序根目录路径
"""
- if getattr(sys, 'frozen', False):
+ if getattr(sys, "frozen", False):
# 打包后的可执行文件
- return Path(sys.executable).parent
+ # PyInstaller 会设置 sys._MEIPASS 指向临时解压目录
+ if hasattr(sys, "_MEIPASS"):
+ return Path(sys._MEIPASS)
+ # Nuitka 打包的可执行文件,使用可执行文件所在目录
+ else:
+ return Path(sys.executable).parent
else:
# 开发环境
return Path(__file__).parent.parent.parent
-
+
def get_absolute_path(self, relative_path: Union[str, Path]) -> Path:
"""将相对路径转换为绝对路径
-
+
Args:
relative_path: 相对于app目录的路径,如 'app/config/file.json'
-
+
Returns:
Path: 绝对路径
"""
if isinstance(relative_path, str):
relative_path = Path(relative_path)
-
+
# 如果已经是绝对路径,直接返回
if relative_path.is_absolute():
return relative_path
-
+
# 拼接为绝对路径
absolute_path = self._app_root / relative_path
return absolute_path
-
+
def ensure_directory_exists(self, path: Union[str, Path]) -> Path:
"""确保目录存在,如果不存在则创建
-
+
Args:
path: 目录路径(相对或绝对)
-
+
Returns:
Path: 绝对路径
-
+
Raises:
FileExistsError: 如果路径已存在且为文件
"""
@@ -90,26 +95,27 @@ def ensure_directory_exists(self, path: Union[str, Path]) -> Path:
absolute_path.mkdir(parents=True, exist_ok=True)
return absolute_path
+
# ==================================================
# 路径获取相关函数
# ==================================================
class PathGetter:
"""路径获取器 - 提供各类特定路径的获取方法"""
-
+
def __init__(self, path_manager: PathManager):
"""初始化路径获取器
-
+
Args:
path_manager: 路径管理器实例
"""
self._path_manager = path_manager
-
+
def get_settings_path(self, filename: str = DEFAULT_SETTINGS_FILENAME) -> Path:
"""获取设置文件路径
-
+
Args:
filename: 设置文件名,默认为DEFAULT_SETTINGS_FILENAME
-
+
Returns:
Path: 设置文件的绝对路径
"""
@@ -117,40 +123,46 @@ def get_settings_path(self, filename: str = DEFAULT_SETTINGS_FILENAME) -> Path:
def get_resources_path(self, resource_type: str, filename: str = "") -> Path:
"""获取资源文件路径
-
+
Args:
resource_type: 资源类型,如 'assets' 'icon'等
filename: 文件名
-
+
Returns:
Path: 资源文件的绝对路径
"""
if filename:
- return self._path_manager.get_absolute_path(f"app/resources/{resource_type}/{filename}")
+ return self._path_manager.get_absolute_path(
+ f"app/resources/{resource_type}/{filename}"
+ )
else:
- return self._path_manager.get_absolute_path(f"app/resources/{resource_type}")
-
+ return self._path_manager.get_absolute_path(
+ f"app/resources/{resource_type}"
+ )
+
def get_config_path(self, config_type: str, filename: str = "") -> Path:
"""获取配置文件路径
-
+
Args:
config_type: 配置类型,如 'reward', 'list'等
filename: 文件名
-
+
Returns:
Path: 配置文件的绝对路径
"""
if filename:
- return self._path_manager.get_absolute_path(f"app/config/{config_type}/{filename}")
+ return self._path_manager.get_absolute_path(
+ f"app/config/{config_type}/{filename}"
+ )
else:
return self._path_manager.get_absolute_path(f"app/config/{config_type}")
-
+
def get_temp_path(self, filename: str = "") -> Path:
"""获取临时文件路径
-
+
Args:
filename: 临时文件名
-
+
Returns:
Path: 临时文件的绝对路径
"""
@@ -158,78 +170,84 @@ def get_temp_path(self, filename: str = "") -> Path:
return self._path_manager.get_absolute_path(f"app/Temp/{filename}")
else:
return self._path_manager.get_absolute_path("app/Temp")
-
+
def get_audio_path(self, filename: str) -> Path:
"""获取音频文件路径
-
+
Args:
filename: 音频文件名
-
+
Returns:
Path: 音频文件的绝对路径
"""
return self._path_manager.get_absolute_path(f"app/resources/audio/{filename}")
-
+
def get_font_path(self, filename: str = DEFAULT_FONT_FILENAME_PRIMARY) -> Path:
"""获取字体文件路径
-
+
Args:
filename: 字体文件名,默认为DEFAULT_FONT_FILENAME_PRIMARY
-
+
Returns:
Path: 字体文件的绝对路径
"""
return self._path_manager.get_absolute_path(f"app/resources/font/{filename}")
+
# ==================================================
# 文件操作相关函数
# ==================================================
class FileOperations:
"""文件操作器 - 提供文件相关的操作方法"""
-
+
def __init__(self, path_manager: PathManager):
"""初始化文件操作器
-
+
Args:
path_manager: 路径管理器实例
"""
self._path_manager = path_manager
-
+
def file_exists(self, path: Union[str, Path]) -> bool:
"""检查文件是否存在
-
+
Args:
path: 文件路径(相对或绝对)
-
+
Returns:
bool: 文件是否存在
"""
absolute_path = self._path_manager.get_absolute_path(path)
return absolute_path.exists()
-
- def open_file(self, path: Union[str, Path], mode: str = "r", encoding: str = DEFAULT_FILE_ENCODING):
+
+ def open_file(
+ self,
+ path: Union[str, Path],
+ mode: str = "r",
+ encoding: str = DEFAULT_FILE_ENCODING,
+ ):
"""打开文件
-
+
Args:
path: 文件路径(相对或绝对)
mode: 文件打开模式
encoding: 文件编码,默认为DEFAULT_FILE_ENCODING
-
+
Returns:
文件对象
"""
absolute_path = self._path_manager.get_absolute_path(path)
# 二进制模式下不传递encoding参数
- if 'b' in mode:
+ if "b" in mode:
return open(absolute_path, mode)
return open(absolute_path, mode, encoding=encoding)
-
+
def remove_file(self, path: Union[str, Path]) -> bool:
"""删除文件
-
+
Args:
path: 文件路径(相对或绝对)
-
+
Returns:
bool: 删除是否成功
"""
@@ -243,6 +261,7 @@ def remove_file(self, path: Union[str, Path]) -> bool:
logger.error(f"删除文件失败: {path}, 错误: {e}")
return False
+
# ==================================================
# 全局实例和便捷函数
# ==================================================
@@ -253,140 +272,154 @@ def remove_file(self, path: Union[str, Path]) -> bool:
path_getter = PathGetter(path_manager)
file_operations = FileOperations(path_manager)
+
# ==================================================
# 路径处理便捷函数列表
# ==================================================
# 1. 基础路径操作
def get_path(relative_path: Union[str, Path]) -> Path:
"""获取绝对路径的便捷函数
-
+
Args:
relative_path: 相对路径
-
+
Returns:
Path: 绝对路径
"""
return path_manager.get_absolute_path(relative_path)
+
def ensure_dir(path: Union[str, Path]) -> Path:
"""确保目录存在的便捷函数
-
+
Args:
path: 目录路径
-
+
Returns:
Path: 绝对路径
"""
return path_manager.ensure_directory_exists(path)
+
def get_app_root() -> Path:
"""获取应用程序根目录的便捷函数
-
+
Returns:
Path: 应用程序根目录路径
"""
return path_manager._app_root
+
# 2. 文件操作便捷函数
def file_exists(path: Union[str, Path]) -> bool:
"""检查文件是否存在的便捷函数
-
+
Args:
path: 文件路径
-
+
Returns:
bool: 文件是否存在
"""
return file_operations.file_exists(path)
-def open_file(path: Union[str, Path], mode: str = "r", encoding: str = DEFAULT_FILE_ENCODING):
+
+def open_file(
+ path: Union[str, Path], mode: str = "r", encoding: str = DEFAULT_FILE_ENCODING
+):
"""打开文件的便捷函数
-
+
Args:
path: 文件路径
mode: 文件打开模式
encoding: 文件编码,默认为DEFAULT_FILE_ENCODING
-
+
Returns:
文件对象
"""
return file_operations.open_file(path, mode, encoding)
+
def remove_file(path: Union[str, Path]) -> bool:
"""删除文件的便捷函数
-
+
Args:
path: 文件路径
-
+
Returns:
bool: 删除是否成功
"""
return file_operations.remove_file(path)
+
# 3. 特定路径获取便捷函数
def get_settings_path(filename: str = DEFAULT_SETTINGS_FILENAME) -> Path:
"""获取设置文件路径的便捷函数
-
+
Args:
filename: 设置文件名,默认为DEFAULT_SETTINGS_FILENAME
-
+
Returns:
Path: 设置文件的绝对路径
"""
return path_getter.get_settings_path(filename)
+
def get_resources_path(config_type: str, filename: str = "") -> Path:
"""获取资源文件路径的便捷函数
-
+
Args:
config_type: 资源类型,如 'assets', 'icon'等
filename: 文件名
-
+
Returns:
Path: 资源文件的绝对路径
"""
return path_getter.get_resources_path(config_type, filename)
+
def get_config_path(config_type: str, filename: str = "") -> Path:
"""获取配置文件路径的便捷函数
-
+
Args:
config_type: 配置类型,如 'reward', 'list'等
filename: 文件名
-
+
Returns:
Path: 配置文件的绝对路径
"""
return path_getter.get_config_path(config_type, filename)
+
def get_temp_path(filename: str = "") -> Path:
"""获取临时文件路径的便捷函数
-
+
Args:
filename: 临时文件名
-
+
Returns:
Path: 临时文件的绝对路径
"""
return path_getter.get_temp_path(filename)
+
def get_audio_path(filename: str) -> Path:
"""获取音频文件路径的便捷函数
-
+
Args:
filename: 音频文件名
-
+
Returns:
Path: 音频文件的绝对路径
"""
return path_getter.get_audio_path(filename)
+
def get_font_path(filename: str = DEFAULT_FONT_FILENAME_PRIMARY) -> Path:
"""获取字体文件路径的便捷函数
-
+
Args:
filename: 字体文件名,默认为DEFAULT_FONT_FILENAME_PRIMARY
-
+
Returns:
Path: 字体文件的绝对路径
"""
diff --git a/app/tools/personalised.py b/app/tools/personalised.py
index 57b3853e..0adff477 100644
--- a/app/tools/personalised.py
+++ b/app/tools/personalised.py
@@ -1,11 +1,11 @@
# ==================================================
# 导入模块
# ==================================================
-from qfluentwidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtWidgets import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
+from qfluentwidgets import *
+from PySide6.QtGui import *
+from PySide6.QtWidgets import *
+from PySide6.QtCore import *
+from PySide6.QtNetwork import *
import json
from loguru import logger
@@ -14,29 +14,30 @@
from app.tools.path_utils import *
from app.tools.settings_access import *
+
# ==================================================
# 字体设置相关函数
# ==================================================
def load_custom_font():
"""加载自定义字体,根据用户设置决定是否加载字体
-
+
Returns:
str: 字体家族名称,如果加载失败则返回 None
"""
- if hasattr(load_custom_font, '_font_cache') and load_custom_font._font_cache:
+ if hasattr(load_custom_font, "_font_cache") and load_custom_font._font_cache:
return load_custom_font._font_cache
-
+
try:
custom_settings_path = get_settings_path()
font_family_setting = _get_font_family_setting(custom_settings_path)
-
+
# 根据字体设置加载相应字体
if font_family_setting:
font_family = _load_font_by_setting(font_family_setting)
if font_family:
load_custom_font._font_cache = font_family
return font_family
-
+
font_family = _load_default_font()
load_custom_font._font_cache = font_family
return font_family
@@ -46,112 +47,117 @@ def load_custom_font():
load_custom_font._font_cache = font_family
return font_family
+
def _get_font_family_setting(settings_path):
"""从设置文件中获取字体家族设置
-
+
Args:
settings_path: 设置文件路径
-
+
Returns:
str: 字体家族设置,如果文件不存在或读取失败则返回空字符串
"""
if not settings_path.is_file():
return ""
-
+
try:
- with open_file(settings_path, 'r', encoding='utf-8') as f:
+ with open_file(settings_path, "r", encoding="utf-8") as f:
settings = json.load(f)
- personal_settings = settings.get('personal', {})
- return personal_settings.get('font_family', '')
+ personal_settings = settings.get("personal", {})
+ return personal_settings.get("font_family", "")
except Exception as e:
logger.error(f"读取字体设置失败: {e}")
return ""
+
def _load_font_by_setting(font_family_setting):
"""根据字体设置加载对应的字体
-
+
Args:
font_family_setting: 字体家族设置
-
+
Returns:
str: 加载成功的字体家族名称,失败则返回 None
"""
font_map = {
"汉仪文黑-85W": "汉仪文黑-85W.ttf",
- "HarmonyOS Sans SC": "HarmonyOS_Sans_SC_Bold.ttf"
+ "HarmonyOS Sans SC": "HarmonyOS_Sans_SC_Bold.ttf",
}
-
+
if font_family_setting in font_map:
font_file = font_map[font_family_setting]
- font_path = get_resources_path('font', font_file)
+ font_path = get_resources_path("font", font_file)
font_id = QFontDatabase.addApplicationFont(str(font_path))
-
+
if font_id < 0:
logger.error(f"加载自定义字体失败: {font_path}")
return None
-
+
font_family = QFontDatabase.applicationFontFamilies(font_id)[0]
return font_family
else:
# 使用系统默认字体
return font_family_setting
+
def _load_default_font():
"""加载默认字体
-
+
Returns:
str: 加载成功的字体家族名称
"""
- font_path = get_resources_path('font', 'HarmonyOS_Sans_SC_Bold.ttf')
+ font_path = get_resources_path("font", "HarmonyOS_Sans_SC_Bold.ttf")
font_id = QFontDatabase.addApplicationFont(str(font_path))
-
+
if font_id < 0:
logger.error(f"加载默认字体失败: {font_path}")
return None
-
+
font_family = QFontDatabase.applicationFontFamilies(font_id)[0]
return font_family
+
# ==================================================
# 图标相关类和函数
# ==================================================
class FluentSystemIcons(FluentFontIconBase):
"""Fluent System Icons 字体图标类"""
-
+
def __init__(self, char):
"""初始化字体图标
-
+
Args:
char: 图标字符
"""
super().__init__(char)
-
+
def path(self, theme=Theme.AUTO):
"""返回字体文件路径"""
- return str(get_resources_path('assets', 'FluentSystemIcons-Filled.ttf'))
-
+ return str(get_resources_path("assets", "FluentSystemIcons-Filled.ttf"))
+
def iconNameMapPath(self):
"""返回图标名称到图标码点的映射表文件路径"""
- return str(get_resources_path('assets', 'FluentSystemIcons-Filled.json'))
+ return str(get_resources_path("assets", "FluentSystemIcons-Filled.json"))
+
def get_theme_icon(icon_name):
"""获取主题相关的图标
-
+
Args:
icon_name: 图标名称或码点
-
+
Returns:
QIcon: 图标对象
"""
try:
# 尝试使用名称获取图标
- if isinstance(icon_name, str) and not icon_name.startswith('\\u'):
+ if isinstance(icon_name, str) and not icon_name.startswith("\\u"):
# 尝试从JSON文件中直接获取码点
try:
- map_path = get_resources_path('assets', 'FluentSystemIcons-Filled.json')
- with open(map_path, 'r', encoding='utf-8') as f:
+ map_path = get_resources_path("assets", "FluentSystemIcons-Filled.json")
+ with open(map_path, "r", encoding="utf-8") as f:
icon_map = json.load(f)
-
+
if icon_name in icon_map:
# 获取图标码点并转换为字符串
code_point = icon_map[icon_name]
@@ -180,16 +186,17 @@ def get_theme_icon(icon_name):
# 返回空的QIcon作为最后备选
return QIcon()
+
def _convert_icon_name_to_char(icon_name):
"""将图标名称或码点转换为字符
-
+
Args:
icon_name: 图标名称或码点
-
+
Returns:
str: 图标字符
"""
- if isinstance(icon_name, str) and icon_name.startswith('\\u'):
+ if isinstance(icon_name, str) and icon_name.startswith("\\u"):
# 将Unicode字符串转换为字符
code_point = int(icon_name[2:], 16)
return chr(code_point)
@@ -200,15 +207,16 @@ def _convert_icon_name_to_char(icon_name):
# 直接使用字符
return icon_name
+
# ==================================================
# 主题相关函数
# ==================================================
def is_dark_theme(qconfig):
"""判断当前是否为深色主题
-
+
Args:
qconfig: 配置对象
-
+
Returns:
bool: 是否为深色主题
"""
@@ -218,24 +226,26 @@ def is_dark_theme(qconfig):
else:
return qconfig.theme == Theme.DARK
+
def setThemeColor(color):
"""设置主题色
-
+
Args:
color: 主题色,可以是 QColor、Qt.GlobalColor 或字符串(十六进制颜色或颜色名称)
"""
hex_color = _convert_color_to_hex(color)
if hex_color is None:
return
-
+
# 设置主题色
themeColor.value = hex_color
# 保存配置
update_settings("basic_settings", "theme_color", hex_color)
+
def themeColor():
"""获取当前主题色
-
+
Returns:
str: 十六进制格式的主题色
"""
@@ -247,12 +257,13 @@ def themeColor():
# 返回默认主题色
return FALLBACK_THEME_COLOR
+
def _convert_color_to_hex(color):
"""将各种格式的颜色转换为十六进制格式
-
+
Args:
color: 颜色对象,可以是 QColor、Qt.GlobalColor 或字符串
-
+
Returns:
str: 十六进制格式的颜色,转换失败则返回 None
"""
@@ -262,7 +273,7 @@ def _convert_color_to_hex(color):
return QColor(color).name()
elif isinstance(color, str):
# 检查是否是十六进制颜色
- if color.startswith('#'):
+ if color.startswith("#"):
return color
else:
# 尝试解析颜色名称
@@ -274,4 +285,4 @@ def _convert_color_to_hex(color):
return None
else:
logger.error(f"不支持的颜色类型: {type(color)}")
- return None
\ No newline at end of file
+ return None
diff --git a/app/tools/result_display.py b/app/tools/result_display.py
index 7a457d30..646ef0c9 100644
--- a/app/tools/result_display.py
+++ b/app/tools/result_display.py
@@ -1,15 +1,13 @@
# ==================================================
# 导入库
# ==================================================
-import json
import random
import colorsys
-from loguru import logger
-from PyQt6.QtWidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from PySide6.QtNetwork import *
from qfluentwidgets import *
from app.tools.variable import *
@@ -18,24 +16,41 @@
from app.tools.settings_default import *
from app.tools.settings_access import *
from app.Language.obtain_language import *
+from app.tools.list import *
+
+from random import SystemRandom
+system_random = SystemRandom()
# ==================================================
# 结果显示工具类
# ==================================================
-
class ResultDisplayUtils:
"""结果显示工具类,提供通用的结果显示功能"""
-
+
+ _color_cache = {}
+
+ @staticmethod
+ def _clear_color_cache():
+ """清除颜色缓存"""
+ ResultDisplayUtils._color_cache.clear()
+
+ @staticmethod
+ def _init_theme_listener():
+ """初始化主题变化监听器"""
+ if not hasattr(ResultDisplayUtils, "_theme_listener_initialized"):
+ qconfig.themeChanged.connect(ResultDisplayUtils._clear_color_cache)
+ ResultDisplayUtils._theme_listener_initialized = True
+
@staticmethod
def _create_avatar_widget(image_path, name, font_size):
"""
创建头像组件
-
+
参数:
image_path: 图片路径
name: 学生姓名
font_size: 字体大小
-
+
返回:
AvatarWidget: 创建的头像组件
"""
@@ -44,23 +59,49 @@ def _create_avatar_widget(image_path, name, font_size):
else:
avatar = AvatarWidget()
avatar.setText(name)
-
+
return avatar
-
+
@staticmethod
- def _format_student_text(display_format, student_id_str, name, draw_count):
+ def _format_student_text(class_name, display_format, student_id_str, name, draw_count, is_group_mode=False, show_random=0):
"""
格式化学生显示文本
-
+
参数:
display_format: 显示格式 (0:学号+姓名, 1:仅姓名, 2:仅学号)
student_id_str: 学号字符串
- name: 学生姓名
+ name: 学生姓名或小组名称
draw_count: 抽取人数
-
+ is_group_mode: 是否为小组模式
+ show_random: 随机组员显示格式 (0:不显示, 1:组名[换行]姓名, 2:组名[短横杠]姓名)
+
返回:
str: 格式化后的文本
"""
+ # 小组模式下,根据show_random设置显示格式
+ if is_group_mode:
+ # 获取小组成员列表
+ group_members = get_group_members(class_name, name)
+
+ if show_random == 1: # 组名[换行]随机选择的成员
+ if group_members:
+ # 随机选择一个成员
+ selected_member = system_random.choice(group_members)
+ selected_name = selected_member["name"]
+ return f"{name}\n{selected_name}"
+ else:
+ return name
+ elif show_random == 2: # 组名[短横杠]随机选择的成员
+ if group_members:
+ # 随机选择一个成员
+ selected_member = system_random.choice(group_members)
+ selected_name = selected_member["name"]
+ return f"{name} - {selected_name}"
+ else:
+ return name
+ else: # 不显示特殊格式 - 只显示组名
+ return f"{name}"
+
if display_format == 1: # 仅显示姓名
return f"{name}"
elif display_format == 2: # 仅显示学号
@@ -70,58 +111,64 @@ def _format_student_text(display_format, student_id_str, name, draw_count):
return f"{student_id_str}\n{name}"
else:
return f"{student_id_str} {name}"
-
+
@staticmethod
- def _create_student_label_with_avatar(image_path, name, font_size, draw_count, text):
+ def _create_student_label_with_avatar(
+ image_path, name, font_size, draw_count, text
+ ):
"""
创建带头像的学生标签
-
+
参数:
image_path: 图片路径
name: 学生姓名
font_size: 字体大小
draw_count: 抽取人数
text: 显示文本
-
+
返回:
QWidget: 包含头像和文本的容器组件
"""
# 创建水平布局
h_layout = QHBoxLayout()
- h_layout.setSpacing(8)
+ h_layout.setSpacing(AVATAR_LABEL_SPACING)
h_layout.setContentsMargins(0, 0, 0, 0)
-
+
# 创建容器widget
container = QWidget()
container.setLayout(h_layout)
-
+
# 创建头像
avatar = ResultDisplayUtils._create_avatar_widget(image_path, name, font_size)
avatar.setRadius(font_size * 2 if draw_count == 1 else font_size // 2)
-
+
# 创建文本标签
text_label = BodyLabel(text)
-
+
# 添加到布局
h_layout.addWidget(avatar)
h_layout.addWidget(text_label)
-
+
return container
-
+
@staticmethod
def _apply_label_style(label, font_size, animation_color):
"""
应用标签样式
-
+
参数:
label: 标签组件
font_size: 字体大小
animation_color: 动画颜色模式
"""
- fixed_color = readme_settings_async("roll_call_settings", "animation_fixed_color")
- # 根据label类型应用不同的样式设置
- if isinstance(label, QWidget) and hasattr(label, 'layout') and label.layout() is not None:
- # 如果是容器类型,对容器内的文本标签应用样式
+ fixed_color = readme_settings_async(
+ "roll_call_settings", "animation_fixed_color"
+ )
+ if (
+ isinstance(label, QWidget)
+ and hasattr(label, "layout")
+ and label.layout() is not None
+ ):
layout = label.layout()
if layout:
for i in range(layout.count()):
@@ -130,32 +177,41 @@ def _apply_label_style(label, font_size, animation_color):
if isinstance(widget, BodyLabel):
widget.setAlignment(Qt.AlignmentFlag.AlignCenter)
style_sheet = f"font-size: {font_size}pt; "
-
+
if animation_color == 1:
style_sheet += f"color: {ResultDisplayUtils._generate_vibrant_color()};"
elif animation_color == 2:
style_sheet += f"color: {fixed_color};"
-
+
widget.setStyleSheet(style_sheet)
else:
- # 如果是普通的BodyLabel,直接应用样式
label.setAlignment(Qt.AlignmentFlag.AlignCenter)
style_sheet = f"font-size: {font_size}pt; "
- fixed_color = readme_settings_async("roll_call_settings", "animation_fixed_color")
+ fixed_color = readme_settings_async(
+ "roll_call_settings", "animation_fixed_color"
+ )
if animation_color == 1:
style_sheet += f"color: {ResultDisplayUtils._generate_vibrant_color()};"
elif animation_color == 2:
style_sheet += f"color: {fixed_color};"
-
+
label.setStyleSheet(style_sheet)
@staticmethod
- def create_student_label(selected_students, draw_count=1, font_size=50,
- animation_color=0, display_format=0,
- show_student_image=False, group_index=0):
+ def create_student_label(
+ class_name,
+ selected_students,
+ draw_count=1,
+ font_size=50,
+ animation_color=0,
+ display_format=0,
+ show_student_image=False,
+ group_index=0,
+ show_random=0,
+ ):
"""
创建学生显示标签
-
+
参数:
selected_students: 选中的学生列表 [(num, selected, exist), ...]
draw_count: 抽取人数
@@ -164,18 +220,22 @@ def create_student_label(selected_students, draw_count=1, font_size=50,
display_format: 显示格式 (0:学号+姓名, 1:仅姓名, 2:仅学号)
show_student_image: 是否显示学生头像
group_index: 小组索引 (0:全班, 1:随机小组, >1:指定小组)
-
+ show_random: 随机组员显示格式 (0:不显示, 1:组名[换行]姓名, 2:组名[短横杠]姓名)
+
返回:
list: 创建的标签列表
"""
student_labels = []
-
+
for num, selected, exist in selected_students:
current_image_path = None
+ # 在小组模式下,尝试使用小组名称作为图片文件名
if show_student_image:
- image_extensions = ['.png', '.jpg', '.jpeg', '.svg']
- for ext in image_extensions:
- temp_path = get_resources_path("images", f"students/{selected}{ext}")
+ image_name = str(selected)
+ for ext in SUPPORTED_IMAGE_EXTENSIONS:
+ temp_path = get_resources_path(
+ "images", f"students/{image_name}{ext}"
+ )
if file_exists(temp_path):
current_image_path = str(temp_path)
break
@@ -183,37 +243,103 @@ def create_student_label(selected_students, draw_count=1, font_size=50,
current_image_path = None
continue
- student_id_str = f"{num:02}"
+ # 处理学号格式化
+ if num is not None:
+ student_id_str = STUDENT_ID_FORMAT.format(num=num)
+ else:
+ student_id_str = ""
+
+ # 处理不同模式下的名称显示
if len(str(selected)) == 2 and group_index == 0:
- name = f"{str(selected)[0]} {str(selected)[1]}"
+ name = f"{str(selected)[0]}{NAME_SPACING}{str(selected)[1]}"
else:
name = str(selected)
- text = ResultDisplayUtils._format_student_text(display_format, student_id_str, name, draw_count)
+
+ text = ResultDisplayUtils._format_student_text(
+ class_name, display_format, student_id_str, name, draw_count, is_group_mode=(group_index == 1), show_random=show_random
+ )
+
+ # 在小组模式下,只在没有成员显示时才显示头像
if show_student_image:
label = ResultDisplayUtils._create_student_label_with_avatar(
current_image_path, name, font_size, draw_count, text
- )
+ )
else:
label = BodyLabel(text)
+
ResultDisplayUtils._apply_label_style(label, font_size, animation_color)
student_labels.append(label)
-
+
return student_labels
-
+
@staticmethod
- def _generate_vibrant_color():
- """生成鲜艳的颜色"""
- hue = random.random()
- saturation = 0.7 + random.random() * 0.3 # 0.7-1.0 之间
- lightness = 0.4 + random.random() * 0.2 # 0.4-0.6 之间
- r, g, b = colorsys.hsv_to_rgb(hue, saturation, lightness)
- return f"#{int(r*255):02x}{int(g*255):02x}{int(b*255):02x}"
-
+ def _generate_vibrant_color(
+ min_saturation=DEFAULT_MIN_SATURATION,
+ max_saturation=DEFAULT_MAX_SATURATION,
+ min_value=DEFAULT_MIN_VALUE,
+ max_value=DEFAULT_MAX_VALUE,
+ use_cache=True,
+ ):
+ """生成鲜艳直观的颜色
+
+ 根据当前主题自动调整颜色明亮程度:
+ - 浅色主题:降低亮度范围,避免颜色过于明亮导致看不清
+ - 深色主题:使用正常亮度范围,确保颜色在深色背景上清晰可见
+
+ 参数:
+ min_saturation: 最小饱和度 (默认DEFAULT_MIN_SATURATION)
+ max_saturation: 最大饱和度 (默认DEFAULT_MAX_SATURATION)
+ min_value: 最小亮度值 (默认DEFAULT_MIN_VALUE)
+ max_value: 最大亮度值 (默认DEFAULT_MAX_VALUE)
+ use_cache: 是否使用颜色缓存 (默认True)
+
+ 返回:
+ str: RGB格式的颜色字符串,如"rgb(255,100,50)"
+ """
+ ResultDisplayUtils._init_theme_listener()
+ if qconfig.theme == Theme.LIGHT: # 浅色主题
+ adjusted_min_value = min(
+ min_value * LIGHT_VALUE_MULTIPLIER, LIGHT_THEME_MAX_VALUE
+ )
+ adjusted_max_value = min(
+ max_value * LIGHT_MAX_VALUE_MULTIPLIER, LIGHT_THEME_ADJUSTED_MAX_VALUE
+ )
+ elif qconfig.theme == Theme.AUTO: # 自动主题
+ lightness = QApplication.palette().color(QPalette.Window).lightness()
+ if lightness > LIGHTNESS_THRESHOLD: # 浅色主题
+ adjusted_min_value = min(
+ min_value * LIGHT_VALUE_MULTIPLIER, LIGHT_THEME_MAX_VALUE
+ )
+ adjusted_max_value = min(
+ max_value * LIGHT_MAX_VALUE_MULTIPLIER,
+ LIGHT_THEME_ADJUSTED_MAX_VALUE,
+ )
+ else: # 深色主题
+ adjusted_min_value = min(
+ min_value * DARK_VALUE_MULTIPLIER, DARK_THEME_MIN_VALUE
+ )
+ adjusted_max_value = max(
+ max_value * DARK_MAX_VALUE_MULTIPLIER, DARK_THEME_MAX_VALUE
+ )
+ else: # 深色主题或其他主题
+ adjusted_min_value = min(
+ min_value * DARK_VALUE_MULTIPLIER, DARK_THEME_MIN_VALUE
+ )
+ adjusted_max_value = max(
+ max_value * DARK_MAX_VALUE_MULTIPLIER, DARK_THEME_MAX_VALUE
+ )
+ h = random.random()
+ s = random.uniform(min_saturation, max_saturation)
+ v = random.uniform(adjusted_min_value, adjusted_max_value)
+ r, g, b = (int(c * 255) for c in colorsys.hsv_to_rgb(h, s, v))
+ color_str = RGB_COLOR_FORMAT.format(r=r, g=g, b=b)
+ return color_str
+
@staticmethod
def display_results_in_grid(result_grid, student_labels, alignment=None):
"""
在网格布局中显示结果
-
+
参数:
result_grid: QGridLayout 网格布局
student_labels: 学生标签列表
@@ -221,15 +347,49 @@ def display_results_in_grid(result_grid, student_labels, alignment=None):
"""
if alignment is None:
alignment = Qt.AlignmentFlag.AlignCenter
-
result_grid.setAlignment(alignment)
while result_grid.count():
item = result_grid.takeAt(0)
if item.widget():
item.widget().deleteLater()
-
- columns = 3
+ container = QWidget()
+ grid_layout = QGridLayout()
+ grid_layout.setSpacing(GRID_LAYOUT_SPACING) # 设置统一的间距
+ grid_layout.setHorizontalSpacing(GRID_HORIZONTAL_SPACING) # 设置水平间距
+ grid_layout.setVerticalSpacing(GRID_VERTICAL_SPACING) # 设置垂直间距
+ container.setLayout(grid_layout)
+ if student_labels:
+ parent_widget = result_grid.parentWidget()
+ if parent_widget:
+ available_width = parent_widget.width() - GRID_ITEM_MARGIN
+ else:
+ available_width = DEFAULT_AVAILABLE_WIDTH
+ total_width = (
+ sum(label.sizeHint().width() for label in student_labels)
+ + len(student_labels) * GRID_ITEM_SPACING
+ )
+ if total_width > available_width:
+ avg_label_width = total_width / len(student_labels)
+ max_columns = max(1, int(available_width // avg_label_width))
+ else:
+ max_columns = len(student_labels)
+ else:
+ max_columns = 1
for i, label in enumerate(student_labels):
- row = i // columns
- col = i % columns
- result_grid.addWidget(label, row, col)
\ No newline at end of file
+ row = i // max_columns
+ col = i % max_columns
+ grid_layout.addWidget(label, row, col)
+ result_grid.addWidget(container)
+
+ @staticmethod
+ def clear_grid(result_grid):
+ """
+ 清空网格布局中的所有小部件
+
+ 参数:
+ result_grid: QGridLayout 网格布局
+ """
+ while result_grid.count():
+ item = result_grid.takeAt(0)
+ if item.widget():
+ item.widget().deleteLater()
diff --git a/app/tools/settings_access.py b/app/tools/settings_access.py
index 14c52f75..e82d419b 100644
--- a/app/tools/settings_access.py
+++ b/app/tools/settings_access.py
@@ -1,11 +1,11 @@
# ==================================================
# 导入模块
# ==================================================
-from qfluentwidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtWidgets import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
+from qfluentwidgets import *
+from PySide6.QtGui import *
+from PySide6.QtWidgets import *
+from PySide6.QtCore import *
+from PySide6.QtNetwork import *
import json
import asyncio
@@ -16,18 +16,20 @@
from app.tools.path_utils import *
from app.tools.settings_default import *
+
# ==================================================
# 设置访问函数
# ==================================================
class SettingsReaderWorker(QObject):
"""设置读取工作线程"""
- finished = pyqtSignal(object) # 信号,传递读取结果
-
+
+ finished = Signal(object) # 信号,传递读取结果
+
def __init__(self, first_level_key: str, second_level_key: str):
super().__init__()
self.first_level_key = first_level_key
self.second_level_key = second_level_key
-
+
def run(self):
"""执行设置读取操作"""
try:
@@ -38,32 +40,43 @@ def run(self):
logger.error(f"读取设置失败: {e}")
default_value = self._get_default_value()
self.finished.emit(default_value)
-
+
def _read_setting_value(self):
"""从设置文件或默认设置中读取值"""
settings_path = get_settings_path()
if file_exists(settings_path):
try:
- with open_file(settings_path, 'r', encoding='utf-8') as f:
+ with open_file(settings_path, "r", encoding="utf-8") as f:
settings_data = json.load(f)
- if (self.first_level_key in settings_data and
- self.second_level_key in settings_data[self.first_level_key]):
- return settings_data[self.first_level_key][self.second_level_key]
+ if (
+ self.first_level_key in settings_data
+ and self.second_level_key in settings_data[self.first_level_key]
+ ):
+ return settings_data[self.first_level_key][
+ self.second_level_key
+ ]
except (json.JSONDecodeError, KeyError):
pass
return self._get_default_value()
+
def _get_default_value(self):
"""获取默认设置值"""
- default_setting = _get_default_setting(self.first_level_key, self.second_level_key)
- return (default_setting['default_value']
- if isinstance(default_setting, dict) and 'default_value' in default_setting
- else default_setting)
+ default_setting = _get_default_setting(
+ self.first_level_key, self.second_level_key
+ )
+ return (
+ default_setting["default_value"]
+ if isinstance(default_setting, dict) and "default_value" in default_setting
+ else default_setting
+ )
+
class AsyncSettingsReader(QObject):
"""异步设置读取器,提供简洁的异步读取方式"""
- finished = pyqtSignal(object) # 读取完成信号,携带结果
- error = pyqtSignal(str) # 错误信号
-
+
+ finished = Signal(object) # 读取完成信号,携带结果
+ error = Signal(str) # 错误信号
+
def __init__(self, first_level_key: str, second_level_key: str):
super().__init__()
self.first_level_key = first_level_key
@@ -73,7 +86,7 @@ def __init__(self, first_level_key: str, second_level_key: str):
self._result = None
self._completed = False
self._future = None
-
+
def read_async(self):
"""异步读取设置,返回Future对象"""
self.thread = QThread()
@@ -86,7 +99,7 @@ def read_async(self):
self._future = asyncio.Future()
self.thread.start()
return self._future
-
+
def result(self, timeout=None):
"""等待并返回结果,类似Future的result()方法"""
if self._completed:
@@ -97,11 +110,11 @@ def result(self, timeout=None):
else:
self.thread.wait()
return self._result
-
+
def is_done(self):
"""检查是否已完成"""
return self._completed
-
+
def _handle_result(self, value):
"""处理设置读取结果"""
self._result = value
@@ -110,37 +123,40 @@ def _handle_result(self, value):
self._future.set_result(value)
self.finished.emit(value)
self._cleanup_thread()
-
+
def _cleanup_thread(self):
"""安全地清理线程资源"""
if self.thread and self.thread.isRunning():
self.thread.quit()
self.thread.wait(1000)
+
def readme_settings(first_level_key: str, second_level_key: str):
"""读取设置
-
+
Args:
first_level_key: 第一层的键
second_level_key: 第二层的键
-
+
Returns:
返回设置值
"""
try:
settings_path = get_settings_path()
if file_exists(settings_path):
- with open_file(settings_path, 'r', encoding='utf-8') as f:
+ with open_file(settings_path, "r", encoding="utf-8") as f:
settings_data = json.load(f)
- if (first_level_key in settings_data and
- second_level_key in settings_data[first_level_key]):
+ if (
+ first_level_key in settings_data
+ and second_level_key in settings_data[first_level_key]
+ ):
value = settings_data[first_level_key][second_level_key]
# logger.debug(f"从设置文件读取: {first_level_key}.{second_level_key} = {value}")
return value
-
+
default_setting = _get_default_setting(first_level_key, second_level_key)
- if isinstance(default_setting, dict) and 'default_value' in default_setting:
- default_value = default_setting['default_value']
+ if isinstance(default_setting, dict) and "default_value" in default_setting:
+ default_value = default_setting["default_value"]
else:
default_value = default_setting
# logger.debug(f"使用默认设置: {first_level_key}.{second_level_key} = {default_value}")
@@ -148,106 +164,89 @@ def readme_settings(first_level_key: str, second_level_key: str):
except Exception as e:
logger.error(f"读取设置失败: {e}")
default_setting = _get_default_setting(first_level_key, second_level_key)
- if isinstance(default_setting, dict) and 'default_value' in default_setting:
- return default_setting['default_value']
+ if isinstance(default_setting, dict) and "default_value" in default_setting:
+ return default_setting["default_value"]
return default_setting
+
def readme_settings_async(first_level_key: str, second_level_key: str, timeout=1000):
- """
- 异步读取设置值,如果失败则回退到同步方法
-
+ """异步读取设置(简化版:直接调用同步方法)
+
+ 为保持 API 兼容性而保留,但在 Nuitka 环境下 QTimer 有兼容性问题,
+ 因此直接使用同步方法。实际测试表明同步方法性能已足够好。
+
Args:
first_level_key (str): 第一层的键
second_level_key (str): 第二层的键
- timeout (int, optional): 异步超时时间(毫秒),默认1000ms
-
+ timeout (int, optional): 保留参数,用于兼容性
+
Returns:
Any: 设置值
-
- Example:
- # 直接获取结果,内部自动处理异步和回退
- value = readme_settings_async("appearance", "theme")
"""
- try:
- reader = AsyncSettingsReader(first_level_key, second_level_key)
- future = reader.read_async()
- loop = QEventLoop()
- timeout_timer = QTimer()
- timeout_timer.singleShot(timeout, loop.quit)
- reader.finished.connect(loop.quit)
- reader.error.connect(loop.quit)
- loop.exec()
- if reader.is_done():
- # logger.debug(f"异步读取设置 {first_level_key}.{second_level_key} 成功: {reader.result()}")
- return reader.result()
- else:
- logger.warning(f"异步读取设置 {first_level_key}.{second_level_key} 超时,回退到同步方法")
- return readme_settings(first_level_key, second_level_key)
-
- except Exception as e:
- logger.warning(f"异步读取设置 {first_level_key}.{second_level_key} 失败: {e},回退到同步方法")
- return readme_settings(first_level_key, second_level_key)
+ return readme_settings(first_level_key, second_level_key)
+
def update_settings(first_level_key: str, second_level_key: str, value: Any):
"""更新设置
-
+
Args:
first_level_key: 第一层的键
second_level_key: 第二层的键
value: 要写入的值(可以是任何类型)
-
+
Returns:
bool: 更新是否成功
"""
try:
# 获取设置文件路径
settings_path = get_settings_path()
-
+
# 确保设置目录存在
ensure_dir(settings_path.parent)
-
+
# 读取现有设置
settings_data = {}
if file_exists(settings_path):
- with open_file(settings_path, 'r', encoding='utf-8') as f:
+ with open_file(settings_path, "r", encoding="utf-8") as f:
settings_data = json.load(f)
-
+
# 更新设置
if first_level_key not in settings_data:
settings_data[first_level_key] = {}
-
+
# 直接保存值,不保存嵌套结构
settings_data[first_level_key][second_level_key] = value
-
+
# 写入设置文件
- with open_file(settings_path, 'w', encoding='utf-8') as f:
+ with open_file(settings_path, "w", encoding="utf-8") as f:
json.dump(settings_data, f, ensure_ascii=False, indent=4)
-
+
logger.debug(f"设置更新成功: {first_level_key}.{second_level_key} = {value}")
except Exception as e:
logger.error(f"设置更新失败: {e}")
+
def _get_default_setting(first_level_key: str, second_level_key: str):
"""获取默认设置值
-
+
Args:
first_level_key: 第一层的键
second_level_key: 第二层的键
-
+
Returns:
默认设置值
"""
# 从settings_default模块获取默认值
default_settings = get_default_settings()
-
+
# 检查设置是否存在
if first_level_key in default_settings:
if second_level_key in default_settings[first_level_key]:
setting_info = default_settings[first_level_key][second_level_key]
# 如果是嵌套结构,提取 default_value
- if isinstance(setting_info, dict) and 'default_value' in setting_info:
- return setting_info['default_value']
+ if isinstance(setting_info, dict) and "default_value" in setting_info:
+ return setting_info["default_value"]
# 否则直接返回值
return setting_info
-
+
return None
diff --git a/app/tools/settings_default.py b/app/tools/settings_default.py
index 976142f6..c9ff314d 100644
--- a/app/tools/settings_default.py
+++ b/app/tools/settings_default.py
@@ -15,24 +15,26 @@
Language = DEFAULT_LANGUAGE
+
# ==================================================
# 便捷函数
# ==================================================
def get_default_settings():
"""获取所有默认设置
-
+
Returns:
dict: 包含所有默认设置的层级字典
"""
return DEFAULT_SETTINGS
+
def get_default_setting(first_level_key: str, second_level_key: str):
"""根据键获取指定的默认设置值
-
+
Args:
first_level_key: 第一层的键
second_level_key: 第二层的键
-
+
Returns:
默认设置值,如果不存在则返回None
"""
@@ -41,12 +43,13 @@ def get_default_setting(first_level_key: str, second_level_key: str):
return DEFAULT_SETTINGS[first_level_key][second_level_key]["default_value"]
return None
+
# ==================================================
# 设置文件管理相关函数
# ==================================================
def manage_settings_file():
"""管理设置文件,确保其存在且完整
-
+
该函数会执行以下操作:
1. 如果设置文件不存在,创建带有默认值的设置文件
2. 如果设置文件存在但缺少某些设置项,补全这些设置项
@@ -55,9 +58,9 @@ def manage_settings_file():
try:
settings_file = get_settings_path()
ensure_dir(settings_file.parent)
-
+
default_settings = get_default_settings()
-
+
if not file_exists(settings_file):
logger.info(f"设置文件不存在,创建默认设置文件: {settings_file}")
flat_settings = {}
@@ -66,14 +69,16 @@ def manage_settings_file():
for second_level_key, second_level_value in first_level_value.items():
# 如果默认值为 None,则不写入设置文件
if second_level_value["default_value"] is not None:
- flat_settings[first_level_key][second_level_key] = second_level_value["default_value"]
-
- with open_file(settings_file, 'w', encoding='utf-8') as f:
+ flat_settings[first_level_key][second_level_key] = (
+ second_level_value["default_value"]
+ )
+
+ with open_file(settings_file, "w", encoding="utf-8") as f:
json.dump(flat_settings, f, indent=4, ensure_ascii=False)
return
-
+
try:
- with open_file(settings_file, 'r', encoding='utf-8') as f:
+ with open_file(settings_file, "r", encoding="utf-8") as f:
current_settings = json.load(f)
except Exception as e:
logger.error(f"读取设置文件失败: {e},将重新创建默认设置文件")
@@ -83,16 +88,18 @@ def manage_settings_file():
for second_level_key, second_level_value in first_level_value.items():
# 如果默认值为 None,则不写入设置文件
if second_level_value["default_value"] is not None:
- flat_settings[first_level_key][second_level_key] = second_level_value["default_value"]
-
- with open_file(settings_file, 'w', encoding='utf-8') as f:
+ flat_settings[first_level_key][second_level_key] = (
+ second_level_value["default_value"]
+ )
+
+ with open_file(settings_file, "w", encoding="utf-8") as f:
json.dump(flat_settings, f, indent=4, ensure_ascii=False)
return
-
+
# 检查并更新设置文件
settings_updated = False
updated_settings = {}
-
+
# 处理现有设置项
for first_level_key, first_level_value in current_settings.items():
if first_level_key in default_settings:
@@ -104,31 +111,39 @@ def manage_settings_file():
# 如果值为 null,则不写入更新后的设置
if second_level_value is not None:
# 如果已经是简单值,直接使用
- updated_settings[first_level_key][second_level_key] = second_level_value
+ updated_settings[first_level_key][second_level_key] = (
+ second_level_value
+ )
else:
# 如果是字典结构,提取 default_value
if "default_value" in second_level_value:
# 如果 default_value 为 null,则不写入更新后的设置
if second_level_value["default_value"] is not None:
- updated_settings[first_level_key][second_level_key] = second_level_value["default_value"]
+ updated_settings[first_level_key][
+ second_level_key
+ ] = second_level_value["default_value"]
else:
# 如果没有 default_value,使用整个值(但检查是否为 null)
if second_level_value is not None:
- updated_settings[first_level_key][second_level_key] = second_level_value
-
+ updated_settings[first_level_key][
+ second_level_key
+ ] = second_level_value
+
# 添加缺失的设置项
for first_level_key, first_level_value in default_settings.items():
if first_level_key not in updated_settings:
updated_settings[first_level_key] = {}
-
+
for second_level_key, second_level_value in first_level_value.items():
if second_level_key not in updated_settings[first_level_key]:
# 如果默认值为 None,则不添加到设置文件
if second_level_value["default_value"] is not None:
- updated_settings[first_level_key][second_level_key] = second_level_value["default_value"]
+ updated_settings[first_level_key][second_level_key] = (
+ second_level_value["default_value"]
+ )
settings_updated = True
# logger.debug(f"添加缺失的设置项: {first_level_key}.{second_level_key} = {second_level_value['default_value']}")
-
+
# 移除多余的设置项
for first_level_key in list(current_settings.keys()):
if first_level_key not in default_settings:
@@ -137,21 +152,24 @@ def manage_settings_file():
if first_level_key in updated_settings:
del updated_settings[first_level_key]
continue
-
+
for second_level_key in list(current_settings[first_level_key].keys()):
if second_level_key not in default_settings[first_level_key]:
# logger.debug(f"移除多余的设置项: {first_level_key}.{second_level_key}")
settings_updated = True
- if first_level_key in updated_settings and second_level_key in updated_settings[first_level_key]:
+ if (
+ first_level_key in updated_settings
+ and second_level_key in updated_settings[first_level_key]
+ ):
del updated_settings[first_level_key][second_level_key]
-
+
if settings_updated:
# logger.debug("设置文件已更新")
- with open_file(settings_file, 'w', encoding='utf-8') as f:
+ with open_file(settings_file, "w", encoding="utf-8") as f:
json.dump(updated_settings, f, indent=4, ensure_ascii=False)
else:
# logger.debug("设置文件已是最新,无需更新")
pass
-
+
except Exception as e:
- logger.error(f"管理设置文件时发生错误: {e}")
\ No newline at end of file
+ logger.error(f"管理设置文件时发生错误: {e}")
diff --git a/app/tools/settings_default_storage.py b/app/tools/settings_default_storage.py
index 40c9bcfd..0b3a57fc 100644
--- a/app/tools/settings_default_storage.py
+++ b/app/tools/settings_default_storage.py
@@ -5,1323 +5,473 @@
# ==================================================
DEFAULT_SETTINGS = {
"window": {
- "height": {
- "default_value": 800
- },
- "width": {
- "default_value": 600
- },
- "is_maximized": {
- "default_value": False
- },
- "pre_maximized_height": {
- "default_value": 800
- },
- "pre_maximized_width": {
- "default_value": 600
- },
+ "height": {"default_value": 800},
+ "width": {"default_value": 600},
+ "is_maximized": {"default_value": False},
+ "pre_maximized_height": {"default_value": 800},
+ "pre_maximized_width": {"default_value": 600},
},
"settings": {
- "height": {
- "default_value": 800
- },
- "width": {
- "default_value": 600
- },
- "is_maximized": {
- "default_value": False
- },
- "pre_maximized_height": {
- "default_value": 800
- },
- "pre_maximized_width": {
- "default_value": 600
- },
+ "height": {"default_value": 800},
+ "width": {"default_value": 600},
+ "is_maximized": {"default_value": False},
+ "pre_maximized_height": {"default_value": 800},
+ "pre_maximized_width": {"default_value": 600},
},
"float_position": {
- "height": {
- "default_value": "screen_height_half"
- },
- "width": {
- "default_value": "screen_width_half"
- },
- },
- "home": {
- "title": {
- "default_value": None
- }
+ "height": {"default_value": "screen_height_half"},
+ "width": {"default_value": "screen_width_half"},
},
+ "home": {"title": {"default_value": None}},
"basic_settings": {
- "title": {
- "default_value": None
- },
- "basic_function": {
- "default_value": None
- },
- "data_management": {
- "default_value": None
- },
- "personalised": {
- "default_value": None
- },
- "autostart": {
- "default_value": False
- },
- "check_update": {
- "default_value": True
- },
- "show_startup_window": {
- "default_value": True
- },
- "export_diagnostic_data": {
- "default_value": None
- },
- "export_settings": {
- "default_value": None
- },
- "import_settings": {
- "default_value": None
- },
- "export_all_data": {
- "default_value": None
- },
- "import_all_data": {
- "default_value": None
- },
- "dpiScale": {
- "default_value": "Auto"
- },
- "font": {
- "default_value": DEFAULT_FONT_NAME_PRIMARY
- },
- "theme": {
- "default_value": "AUTO"
- },
- "theme_color": {
- "default_value": DEFAULT_THEME_COLOR
- },
- "language": {
- "default_value": "ZH_CN"
- },
- },
- "list_management": {
- "title": {
- "default_value": None
- }
- },
+ "title": {"default_value": None},
+ "basic_function": {"default_value": None},
+ "data_management": {"default_value": None},
+ "personalised": {"default_value": None},
+ "autostart": {"default_value": False},
+ "check_update": {"default_value": True},
+ "show_startup_window": {"default_value": True},
+ "export_diagnostic_data": {"default_value": None},
+ "export_settings": {"default_value": None},
+ "import_settings": {"default_value": None},
+ "export_all_data": {"default_value": None},
+ "import_all_data": {"default_value": None},
+ "dpiScale": {"default_value": "Auto"},
+ "font": {"default_value": DEFAULT_FONT_NAME_PRIMARY},
+ "theme": {"default_value": "AUTO"},
+ "theme_color": {"default_value": DEFAULT_THEME_COLOR},
+ "language": {"default_value": "ZH_CN"},
+ },
+ "list_management": {"title": {"default_value": None}},
"roll_call_list": {
- "title": {
- "default_value": None
- },
- "set_class_name": {
- "default_value": None
- },
- "select_class_name": {
- "default_value": 0
- },
- "import_student_name": {
- "default_value": None
- },
- "name_setting": {
- "default_value": None
- },
- "gender_setting": {
- "default_value": None
- },
- "group_setting": {
- "default_value": None
- },
- "export_student_name": {
- "default_value": None
- }
+ "title": {"default_value": None},
+ "set_class_name": {"default_value": None},
+ "select_class_name": {"default_value": ""},
+ "import_student_name": {"default_value": None},
+ "name_setting": {"default_value": None},
+ "gender_setting": {"default_value": None},
+ "group_setting": {"default_value": None},
+ "export_student_name": {"default_value": None},
},
"roll_call_table": {
- "title": {
- "default_value": None
- },
- "select_class_name": {
- "default_value": 0
- },
- "HeaderLabels": {
- "default_value": None
- }
- },
- "custom_draw_list": {
- "title": {
- "default_value": None
- }
+ "title": {"default_value": None},
+ "select_class_name": {"default_value": 0},
+ "HeaderLabels": {"default_value": None},
},
+ "custom_draw_list": {"title": {"default_value": None}},
"lottery_list": {
- "title": {
- "default_value": None
- },
- "set_pool_name": {
- "default_value": None
- },
- "select_pool_name": {
- "default_value": 0
- },
- "import_prize_name": {
- "default_value": None
- },
- "prize_setting": {
- "default_value": None
- },
- "prize_weight_setting": {
- "default_value": None
- },
- "export_prize_name": {
- "default_value": None
- }
+ "title": {"default_value": None},
+ "set_pool_name": {"default_value": None},
+ "select_pool_name": {"default_value": 0},
+ "import_prize_name": {"default_value": None},
+ "prize_setting": {"default_value": None},
+ "prize_weight_setting": {"default_value": None},
+ "export_prize_name": {"default_value": None},
},
"lottery_table": {
- "title": {
- "default_value": None
- },
- "select_pool_name": {
- "default_value": 0
- },
- "HeaderLabels": {
- "default_value": None
- }
- },
- "extraction_settings": {
- "title": {
- "default_value": None
- }
+ "title": {"default_value": None},
+ "select_pool_name": {"default_value": 0},
+ "HeaderLabels": {"default_value": None},
},
+ "extraction_settings": {"title": {"default_value": None}},
"roll_call_settings": {
- "title": {
- "default_value": None
- },
- "extraction_function": {
- "default_value": None
- },
- "display_settings": {
- "default_value": None
- },
- "basic_animation_settings": {
- "default_value": None
- },
- "color_theme_settings": {
- "default_value": None
- },
- "student_image_settings": {
- "default_value": None
- },
- "music_settings": {
- "default_value": None
- },
- "draw_mode": {
- "default_value": 1
- },
- "clear_record": {
- "default_value": 0
- },
- "half_repeat": {
- "default_value": 1
- },
- "clear_time": {
- "default_value": 0
- },
- "draw_type": {
- "default_value": 0
- },
- "font_size": {
- "default_value": 50
- },
- "display_format": {
- "default_value": 0
- },
- "show_random": {
- "default_value": 0
- },
- "animation": {
- "default_value": 1
- },
- "animation_interval": {
- "default_value": 80
- },
- "autoplay_count": {
- "default_value": 5
- },
- "animation_color_theme": {
- "default_value": 0
- },
- "animation_fixed_color": {
- "default_value": DEFAULT_THEME_COLOR
- },
- "student_image": {
- "default_value": False
- },
- "open_student_image_folder": {
- "default_value": None
- },
- "animation_music": {
- "default_value": False
- },
- "result_music": {
- "default_value": False
- },
- "open_animation_music_folder": {
- "default_value": None
- },
- "open_result_music_folder": {
- "default_value": None
- },
- "animation_music_volume": {
- "default_value": 30
- },
- "result_music_volume": {
- "default_value": 30
- },
- "animation_music_fade_in": {
- "default_value": 300
- },
- "result_music_fade_in": {
- "default_value": 300
- },
- "animation_music_fade_out": {
- "default_value": 300
- },
- "result_music_fade_out": {
- "default_value": 300
- }
+ "title": {"default_value": None},
+ "extraction_function": {"default_value": None},
+ "display_settings": {"default_value": None},
+ "basic_animation_settings": {"default_value": None},
+ "color_theme_settings": {"default_value": None},
+ "student_image_settings": {"default_value": None},
+ "music_settings": {"default_value": None},
+ "draw_mode": {"default_value": 1},
+ "clear_record": {"default_value": 0},
+ "half_repeat": {"default_value": 1},
+ "draw_type": {"default_value": 0},
+ "font_size": {"default_value": 50},
+ "display_format": {"default_value": 0},
+ "show_random": {"default_value": 0},
+ "animation": {"default_value": 1},
+ "animation_interval": {"default_value": 80},
+ "autoplay_count": {"default_value": 5},
+ "animation_color_theme": {"default_value": 0},
+ "animation_fixed_color": {"default_value": DEFAULT_THEME_COLOR},
+ "student_image": {"default_value": False},
+ "open_student_image_folder": {"default_value": None},
+ "animation_music": {"default_value": False},
+ "result_music": {"default_value": False},
+ "open_animation_music_folder": {"default_value": None},
+ "open_result_music_folder": {"default_value": None},
+ "animation_music_volume": {"default_value": 30},
+ "result_music_volume": {"default_value": 30},
+ "animation_music_fade_in": {"default_value": 300},
+ "result_music_fade_in": {"default_value": 300},
+ "animation_music_fade_out": {"default_value": 300},
+ "result_music_fade_out": {"default_value": 300},
},
"quick_draw_settings": {
- "title": {
- "default_value": None
- },
- "extraction_function": {
- "default_value": None
- },
- "display_settings": {
- "default_value": None
- },
- "basic_animation_settings": {
- "default_value": None
- },
- "color_theme_settings": {
- "default_value": None
- },
- "student_image_settings": {
- "default_value": None
- },
- "music_settings": {
- "default_value": None
- },
- "draw_mode": {
- "default_value": 1
- },
- "clear_record": {
- "default_value": 0
- },
- "half_repeat": {
- "default_value": 1
- },
- "clear_time": {
- "default_value": 0
- },
- "draw_type": {
- "default_value": 0
- },
- "font_size": {
- "default_value": 50
- },
- "display_format": {
- "default_value": 0
- },
- "show_random": {
- "default_value": 0
- },
- "animation": {
- "default_value": 1
- },
- "animation_interval": {
- "default_value": 80
- },
- "autoplay_count": {
- "default_value": 5
- },
- "animation_color_theme": {
- "default_value": 0
- },
- "result_color_theme": {
- "default_value": 0
- },
- "animation_fixed_color": {
- "default_value": DEFAULT_THEME_COLOR
- },
- "result_fixed_color": {
- "default_value": DEFAULT_THEME_COLOR
- },
- "student_image": {
- "default_value": False
- },
- "open_student_image_folder": {
- "default_value": None
- },
- "animation_music": {
- "default_value": False
- },
- "result_music": {
- "default_value": False
- },
- "open_animation_music_folder": {
- "default_value": None
- },
- "open_result_music_folder": {
- "default_value": None
- },
- "animation_music_volume": {
- "default_value": 30
- },
- "result_music_volume": {
- "default_value": 30
- },
- "animation_music_fade_in": {
- "default_value": 300
- },
- "result_music_fade_in": {
- "default_value": 300
- },
- "animation_music_fade_out": {
- "default_value": 300
- },
- "result_music_fade_out": {
- "default_value": 300
- }
+ "title": {"default_value": None},
+ "extraction_function": {"default_value": None},
+ "display_settings": {"default_value": None},
+ "basic_animation_settings": {"default_value": None},
+ "color_theme_settings": {"default_value": None},
+ "student_image_settings": {"default_value": None},
+ "music_settings": {"default_value": None},
+ "draw_mode": {"default_value": 1},
+ "clear_record": {"default_value": 0},
+ "half_repeat": {"default_value": 1},
+ "draw_type": {"default_value": 0},
+ "font_size": {"default_value": 50},
+ "display_format": {"default_value": 0},
+ "show_random": {"default_value": 0},
+ "animation": {"default_value": 1},
+ "animation_interval": {"default_value": 80},
+ "autoplay_count": {"default_value": 5},
+ "animation_color_theme": {"default_value": 0},
+ "result_color_theme": {"default_value": 0},
+ "animation_fixed_color": {"default_value": DEFAULT_THEME_COLOR},
+ "result_fixed_color": {"default_value": DEFAULT_THEME_COLOR},
+ "student_image": {"default_value": False},
+ "open_student_image_folder": {"default_value": None},
+ "animation_music": {"default_value": False},
+ "result_music": {"default_value": False},
+ "open_animation_music_folder": {"default_value": None},
+ "open_result_music_folder": {"default_value": None},
+ "animation_music_volume": {"default_value": 30},
+ "result_music_volume": {"default_value": 30},
+ "animation_music_fade_in": {"default_value": 300},
+ "result_music_fade_in": {"default_value": 300},
+ "animation_music_fade_out": {"default_value": 300},
+ "result_music_fade_out": {"default_value": 300},
},
"instant_draw_settings": {
- "title": {
- "default_value": None
- },
- "extraction_function": {
- "default_value": None
- },
- "display_settings": {
- "default_value": None
- },
- "basic_animation_settings": {
- "default_value": None
- },
- "color_theme_settings": {
- "default_value": None
- },
- "student_image_settings": {
- "default_value": None
- },
- "music_settings": {
- "default_value": None
- },
- "draw_mode": {
- "default_value": 1
- },
- "clear_record": {
- "default_value": 0
- },
- "half_repeat": {
- "default_value": 1
- },
- "clear_time": {
- "default_value": 0
- },
- "draw_type": {
- "default_value": 0
- },
- "font_size": {
- "default_value": 50
- },
- "display_format": {
- "default_value": 0
- },
- "show_random": {
- "default_value": 0
- },
- "animation": {
- "default_value": 1
- },
- "animation_interval": {
- "default_value": 80
- },
- "autoplay_count": {
- "default_value": 5
- },
- "animation_color_theme": {
- "default_value": 0
- },
- "result_color_theme": {
- "default_value": 0
- },
- "animation_fixed_color": {
- "default_value": DEFAULT_THEME_COLOR
- },
- "result_fixed_color": {
- "default_value": DEFAULT_THEME_COLOR
- },
- "student_image": {
- "default_value": False
- },
- "open_student_image_folder": {
- "default_value": None
- },
- "animation_music": {
- "default_value": False
- },
- "result_music": {
- "default_value": False
- },
- "open_animation_music_folder": {
- "default_value": None
- },
- "open_result_music_folder": {
- "default_value": None
- },
- "animation_music_volume": {
- "default_value": 30
- },
- "result_music_volume": {
- "default_value": 30
- },
- "animation_music_fade_in": {
- "default_value": 300
- },
- "result_music_fade_in": {
- "default_value": 300
- },
- "animation_music_fade_out": {
- "default_value": 300
- },
- "result_music_fade_out": {
- "default_value": 300
- }
- },
- "custom_draw_settings": {
- "title": {
- "default_value": None
- }
- },
+ "title": {"default_value": None},
+ "extraction_function": {"default_value": None},
+ "display_settings": {"default_value": None},
+ "basic_animation_settings": {"default_value": None},
+ "color_theme_settings": {"default_value": None},
+ "student_image_settings": {"default_value": None},
+ "music_settings": {"default_value": None},
+ "draw_mode": {"default_value": 1},
+ "clear_record": {"default_value": 0},
+ "half_repeat": {"default_value": 1},
+ "draw_type": {"default_value": 0},
+ "font_size": {"default_value": 50},
+ "display_format": {"default_value": 0},
+ "show_random": {"default_value": 0},
+ "animation": {"default_value": 1},
+ "animation_interval": {"default_value": 80},
+ "autoplay_count": {"default_value": 5},
+ "animation_color_theme": {"default_value": 0},
+ "result_color_theme": {"default_value": 0},
+ "animation_fixed_color": {"default_value": DEFAULT_THEME_COLOR},
+ "result_fixed_color": {"default_value": DEFAULT_THEME_COLOR},
+ "student_image": {"default_value": False},
+ "open_student_image_folder": {"default_value": None},
+ "animation_music": {"default_value": False},
+ "result_music": {"default_value": False},
+ "open_animation_music_folder": {"default_value": None},
+ "open_result_music_folder": {"default_value": None},
+ "animation_music_volume": {"default_value": 30},
+ "result_music_volume": {"default_value": 30},
+ "animation_music_fade_in": {"default_value": 300},
+ "result_music_fade_in": {"default_value": 300},
+ "animation_music_fade_out": {"default_value": 300},
+ "result_music_fade_out": {"default_value": 300},
+ },
+ "custom_draw_settings": {"title": {"default_value": None}},
"lottery_settings": {
- "title": {
- "default_value": None
- },
- "extraction_function": {
- "default_value": None
- },
- "display_settings": {
- "default_value": None
- },
- "basic_animation_settings": {
- "default_value": None
- },
- "color_theme_settings": {
- "default_value": None
- },
- "student_image_settings": {
- "default_value": None
- },
- "music_settings": {
- "default_value": None
- },
- "draw_mode": {
- "default_value": 1
- },
- "clear_record": {
- "default_value": 0
- },
- "half_repeat": {
- "default_value": 1
- },
- "clear_time": {
- "default_value": 0
- },
- "draw_type": {
- "default_value": 0
- },
- "font_size": {
- "default_value": 50
- },
- "display_format": {
- "default_value": 0
- },
- "show_random": {
- "default_value": 0
- },
- "animation": {
- "default_value": 1
- },
- "animation_interval": {
- "default_value": 80
- },
- "autoplay_count": {
- "default_value": 5
- },
- "animation_color_theme": {
- "default_value": 0
- },
- "result_color_theme": {
- "default_value": 0
- },
- "animation_fixed_color": {
- "default_value": DEFAULT_THEME_COLOR
- },
- "result_fixed_color": {
- "default_value": DEFAULT_THEME_COLOR
- },
- "lottery_image": {
- "default_value": False
- },
- "open_lottery_image_folder": {
- "default_value": None
- },
- "animation_music": {
- "default_value": False
- },
- "result_music": {
- "default_value": False
- },
- "open_animation_music_folder": {
- "default_value": None
- },
- "open_result_music_folder": {
- "default_value": None
- },
- "animation_music_volume": {
- "default_value": 30
- },
- "result_music_volume": {
- "default_value": 30
- },
- "animation_music_fade_in": {
- "default_value": 300
- },
- "result_music_fade_in": {
- "default_value": 300
- },
- "animation_music_fade_out": {
- "default_value": 300
- },
- "result_music_fade_out": {
- "default_value": 300
- }
- },
- "notification_settings": {
- "title": {
- "default_value": None
- }
- },
+ "title": {"default_value": None},
+ "extraction_function": {"default_value": None},
+ "display_settings": {"default_value": None},
+ "basic_animation_settings": {"default_value": None},
+ "color_theme_settings": {"default_value": None},
+ "student_image_settings": {"default_value": None},
+ "music_settings": {"default_value": None},
+ "draw_mode": {"default_value": 1},
+ "clear_record": {"default_value": 0},
+ "half_repeat": {"default_value": 1},
+ "draw_type": {"default_value": 0},
+ "font_size": {"default_value": 50},
+ "display_format": {"default_value": 0},
+ "show_random": {"default_value": 0},
+ "animation": {"default_value": 1},
+ "animation_interval": {"default_value": 80},
+ "autoplay_count": {"default_value": 5},
+ "animation_color_theme": {"default_value": 0},
+ "result_color_theme": {"default_value": 0},
+ "animation_fixed_color": {"default_value": DEFAULT_THEME_COLOR},
+ "result_fixed_color": {"default_value": DEFAULT_THEME_COLOR},
+ "lottery_image": {"default_value": False},
+ "open_lottery_image_folder": {"default_value": None},
+ "animation_music": {"default_value": False},
+ "result_music": {"default_value": False},
+ "open_animation_music_folder": {"default_value": None},
+ "open_result_music_folder": {"default_value": None},
+ "animation_music_volume": {"default_value": 30},
+ "result_music_volume": {"default_value": 30},
+ "animation_music_fade_in": {"default_value": 300},
+ "result_music_fade_in": {"default_value": 300},
+ "animation_music_fade_out": {"default_value": 300},
+ "result_music_fade_out": {"default_value": 300},
+ },
+ "notification_settings": {"title": {"default_value": None}},
"roll_call_notification_settings": {
- "title": {
- "default_value": None
- },
- "basic_settings": {
- "default_value": None
- },
- "window_mode": {
- "default_value": None
- },
- "floating_window_mode": {
- "default_value": None
- },
- "call_notification_service": {
- "default_value": False
- },
- "notification_mode": {
- "default_value": 0
- },
- "animation": {
- "default_value": True
- },
- "enabled_monitor": {
- "default_value": "OFF"
- },
- "window_position": {
- "default_value": 0
- },
- "horizontal_offset": {
- "default_value": 0
- },
- "vertical_offset": {
- "default_value": 0
- },
- "transparency": {
- "default_value": 0.6
- },
- "floating_window_enabled_monitor": {
- "default_value": "OFF"
- },
- "floating_window_position": {
- "default_value": 0
- },
- "floating_window_horizontal_offset": {
- "default_value": 0
- },
- "floating_window_vertical_offset": {
- "default_value": 0
- },
- "floating_window_transparency": {
- "default_value": 0.6
- }
+ "title": {"default_value": None},
+ "basic_settings": {"default_value": None},
+ "window_mode": {"default_value": None},
+ "floating_window_mode": {"default_value": None},
+ "call_notification_service": {"default_value": False},
+ "notification_mode": {"default_value": 0},
+ "animation": {"default_value": True},
+ "enabled_monitor": {"default_value": "OFF"},
+ "window_position": {"default_value": 0},
+ "horizontal_offset": {"default_value": 0},
+ "vertical_offset": {"default_value": 0},
+ "transparency": {"default_value": 0.6},
+ "floating_window_enabled_monitor": {"default_value": "OFF"},
+ "floating_window_position": {"default_value": 0},
+ "floating_window_horizontal_offset": {"default_value": 0},
+ "floating_window_vertical_offset": {"default_value": 0},
+ "floating_window_transparency": {"default_value": 0.6},
},
"quick_draw_notification_settings": {
- "title": {
- "default_value": None
- },
- "basic_settings": {
- "default_value": None
- },
- "window_mode": {
- "default_value": None
- },
- "floating_window_mode": {
- "default_value": None
- },
- "notification_mode": {
- "default_value": 0
- },
- "animation": {
- "default_value": True
- },
- "enabled_monitor": {
- "default_value": "OFF"
- },
- "window_position": {
- "default_value": 0
- },
- "horizontal_offset": {
- "default_value": 0
- },
- "vertical_offset": {
- "default_value": 0
- },
- "transparency": {
- "default_value": 0.6
- },
- "floating_window_enabled_monitor": {
- "default_value": "OFF"
- },
- "floating_window_position": {
- "default_value": 0
- },
- "floating_window_horizontal_offset": {
- "default_value": 0
- },
- "floating_window_vertical_offset": {
- "default_value": 0
- },
- "floating_window_transparency": {
- "default_value": 0.6
- }
+ "title": {"default_value": None},
+ "basic_settings": {"default_value": None},
+ "window_mode": {"default_value": None},
+ "floating_window_mode": {"default_value": None},
+ "notification_mode": {"default_value": 0},
+ "animation": {"default_value": True},
+ "enabled_monitor": {"default_value": "OFF"},
+ "window_position": {"default_value": 0},
+ "horizontal_offset": {"default_value": 0},
+ "vertical_offset": {"default_value": 0},
+ "transparency": {"default_value": 0.6},
+ "floating_window_enabled_monitor": {"default_value": "OFF"},
+ "floating_window_position": {"default_value": 0},
+ "floating_window_horizontal_offset": {"default_value": 0},
+ "floating_window_vertical_offset": {"default_value": 0},
+ "floating_window_transparency": {"default_value": 0.6},
},
"instant_draw_notification_settings": {
- "title": {
- "default_value": None
- },
- "basic_settings": {
- "default_value": None
- },
- "window_mode": {
- "default_value": None
- },
- "floating_window_mode": {
- "default_value": None
- },
- "notification_mode": {
- "default_value": 0
- },
- "animation": {
- "default_value": True
- },
- "enabled_monitor": {
- "default_value": "OFF"
- },
- "window_position": {
- "default_value": 0
- },
- "horizontal_offset": {
- "default_value": 0
- },
- "vertical_offset": {
- "default_value": 0
- },
- "transparency": {
- "default_value": 0.6
- },
- "floating_window_enabled_monitor": {
- "default_value": "OFF"
- },
- "floating_window_position": {
- "default_value": 0
- },
- "floating_window_horizontal_offset": {
- "default_value": 0
- },
- "floating_window_vertical_offset": {
- "default_value": 0
- },
- "floating_window_transparency": {
- "default_value": 0.6
- }
+ "title": {"default_value": None},
+ "basic_settings": {"default_value": None},
+ "window_mode": {"default_value": None},
+ "floating_window_mode": {"default_value": None},
+ "notification_mode": {"default_value": 0},
+ "animation": {"default_value": True},
+ "enabled_monitor": {"default_value": "OFF"},
+ "window_position": {"default_value": 0},
+ "horizontal_offset": {"default_value": 0},
+ "vertical_offset": {"default_value": 0},
+ "transparency": {"default_value": 0.6},
+ "floating_window_enabled_monitor": {"default_value": "OFF"},
+ "floating_window_position": {"default_value": 0},
+ "floating_window_horizontal_offset": {"default_value": 0},
+ "floating_window_vertical_offset": {"default_value": 0},
+ "floating_window_transparency": {"default_value": 0.6},
},
"custom_draw_notification_settings": {
- "title": {
- "default_value": None
- },
- "basic_settings": {
- "default_value": None
- },
- "window_mode": {
- "default_value": None
- },
- "floating_window_mode": {
- "default_value": None
- },
- "call_notification_service": {
- "default_value": False
- },
- "notification_mode": {
- "default_value": 0
- },
- "animation": {
- "default_value": True
- },
- "enabled_monitor": {
- "default_value": "OFF"
- },
- "window_position": {
- "default_value": 0
- },
- "horizontal_offset": {
- "default_value": 0
- },
- "vertical_offset": {
- "default_value": 0
- },
- "transparency": {
- "default_value": 0.6
- },
- "floating_window_enabled_monitor": {
- "default_value": "OFF"
- },
- "floating_window_position": {
- "default_value": 0
- },
- "floating_window_horizontal_offset": {
- "default_value": 0
- },
- "floating_window_vertical_offset": {
- "default_value": 0
- },
- "floating_window_transparency": {
- "default_value": 0.6
- }
+ "title": {"default_value": None},
+ "basic_settings": {"default_value": None},
+ "window_mode": {"default_value": None},
+ "floating_window_mode": {"default_value": None},
+ "call_notification_service": {"default_value": False},
+ "notification_mode": {"default_value": 0},
+ "animation": {"default_value": True},
+ "enabled_monitor": {"default_value": "OFF"},
+ "window_position": {"default_value": 0},
+ "horizontal_offset": {"default_value": 0},
+ "vertical_offset": {"default_value": 0},
+ "transparency": {"default_value": 0.6},
+ "floating_window_enabled_monitor": {"default_value": "OFF"},
+ "floating_window_position": {"default_value": 0},
+ "floating_window_horizontal_offset": {"default_value": 0},
+ "floating_window_vertical_offset": {"default_value": 0},
+ "floating_window_transparency": {"default_value": 0.6},
},
"lottery_notification_settings": {
- "title": {
- "default_value": None
- },
- "basic_settings": {
- "default_value": None
- },
- "window_mode": {
- "default_value": None
- },
- "floating_window_mode": {
- "default_value": None
- },
- "call_notification_service": {
- "default_value": False
- },
- "notification_mode": {
- "default_value": 0
- },
- "animation": {
- "default_value": True
- },
- "enabled_monitor": {
- "default_value": "OFF"
- },
- "window_position": {
- "default_value": 0
- },
- "horizontal_offset": {
- "default_value": 0
- },
- "vertical_offset": {
- "default_value": 0
- },
- "transparency": {
- "default_value": 0.6
- },
- "floating_window_enabled_monitor": {
- "default_value": "OFF"
- },
- "floating_window_position": {
- "default_value": 0
- },
- "floating_window_horizontal_offset": {
- "default_value": 0
- },
- "floating_window_vertical_offset": {
- "default_value": 0
- },
- "floating_window_transparency": {
- "default_value": 0.6
- }
- },
- "safety_settings": {
- "title": {
- "default_value": None
- }
- },
+ "title": {"default_value": None},
+ "basic_settings": {"default_value": None},
+ "window_mode": {"default_value": None},
+ "floating_window_mode": {"default_value": None},
+ "call_notification_service": {"default_value": False},
+ "notification_mode": {"default_value": 0},
+ "animation": {"default_value": True},
+ "enabled_monitor": {"default_value": "OFF"},
+ "window_position": {"default_value": 0},
+ "horizontal_offset": {"default_value": 0},
+ "vertical_offset": {"default_value": 0},
+ "transparency": {"default_value": 0.6},
+ "floating_window_enabled_monitor": {"default_value": "OFF"},
+ "floating_window_position": {"default_value": 0},
+ "floating_window_horizontal_offset": {"default_value": 0},
+ "floating_window_vertical_offset": {"default_value": 0},
+ "floating_window_transparency": {"default_value": 0.6},
+ },
+ "safety_settings": {"title": {"default_value": None}},
"basic_safety_settings": {
- "title": {
- "default_value": None
- },
- "verification_method": {
- "default_value": None
- },
- "verification_process": {
- "default_value": 0
- },
- "security_operations": {
- "default_value": None
- },
- "safety_switch": {
- "default_value": False
- },
- "set_password": {
- "default_value": None
- },
- "totp_switch": {
- "default_value": False
- },
- "set_totp": {
- "default_value": None
- },
- "usb_switch": {
- "default_value": False
- },
- "bind_usb": {
- "default_value": None
- },
- "unbind_usb": {
- "default_value": None
- },
- "show_hide_floating_window_switch": {
- "default_value": False
- },
- "restart_switch": {
- "default_value": False
- },
- "exit_switch": {
- "default_value": False
- }
+ "title": {"default_value": None},
+ "verification_method": {"default_value": None},
+ "verification_process": {"default_value": 0},
+ "security_operations": {"default_value": None},
+ "safety_switch": {"default_value": False},
+ "set_password": {"default_value": None},
+ "totp_switch": {"default_value": False},
+ "set_totp": {"default_value": None},
+ "usb_switch": {"default_value": False},
+ "bind_usb": {"default_value": None},
+ "unbind_usb": {"default_value": None},
+ "show_hide_floating_window_switch": {"default_value": False},
+ "restart_switch": {"default_value": False},
+ "exit_switch": {"default_value": False},
},
"advanced_safety_settings": {
- "title": {
- "default_value": None
- },
- "strong_protection": {
- "default_value": None
- },
- "data_encryption": {
- "default_value": None
- },
- "encryption_strong_switch": {
- "default_value": False
- },
- "encryption_strong_mode": {
- "default_value": 0
- },
- "encryption_list_switch": {
- "default_value": False
- },
- "encryption_history_switch": {
- "default_value": False
- },
- "encryption_temp_switch": {
- "default_value": False
- }
- },
- "custom_settings": {
- "title": {
- "default_value": None
- }
- },
+ "title": {"default_value": None},
+ "strong_protection": {"default_value": None},
+ "data_encryption": {"default_value": None},
+ "encryption_strong_switch": {"default_value": False},
+ "encryption_strong_mode": {"default_value": 0},
+ "encryption_list_switch": {"default_value": False},
+ "encryption_history_switch": {"default_value": False},
+ "encryption_temp_switch": {"default_value": False},
+ },
+ "custom_settings": {"title": {"default_value": None}},
"page_management": {
- "title": {
- "default_value": None
- },
- "roll_call": {
- "default_value": None
- },
- "lottery": {
- "default_value": None
- },
- "roll_call_method": {
- "default_value": 1
- },
- "show_name": {
- "default_value": False
- },
- "reset_roll_call": {
- "default_value": True
- },
- "roll_call_quantity_control": {
- "default_value": True
- },
- "roll_call_start_button": {
- "default_value": True
- },
- "roll_call_list": {
- "default_value": True
- },
- "roll_call_range": {
- "default_value": True
- },
- "roll_call_gender": {
- "default_value": True
- },
- "roll_call_quantity_label": {
- "default_value": True
- },
- "lottery_method": {
- "default_value": 1
- },
- "show_lottery_name": {
- "default_value": False
- },
- "reset_lottery": {
- "default_value": True
- },
- "lottery_quantity_control": {
- "default_value": True
- },
- "lottery_start_button": {
- "default_value": True
- },
- "lottery_list": {
- "default_value": True
- },
- "lottery_quantity_label": {
- "default_value": True
- },
- "custom_method": {
- "default_value": 1
- },
- "reset_custom": {
- "default_value": True
- },
- "custom_quantity_control": {
- "default_value": True
- },
- "custom_start_button": {
- "default_value": True
- },
- "custom_list": {
- "default_value": True
- },
- "custom_range_start": {
- "default_value": True
- },
- "custom_range_end": {
- "default_value": True
- },
- "draw_custom_method": {
- "default_value": True
- },
- "custom_quantity_label": {
- "default_value": True
- }
+ "title": {"default_value": None},
+ "roll_call": {"default_value": None},
+ "lottery": {"default_value": None},
+ "roll_call_method": {"default_value": 1},
+ "show_name": {"default_value": False},
+ "reset_roll_call": {"default_value": True},
+ "roll_call_quantity_control": {"default_value": True},
+ "roll_call_start_button": {"default_value": True},
+ "roll_call_list": {"default_value": True},
+ "roll_call_range": {"default_value": True},
+ "roll_call_gender": {"default_value": True},
+ "roll_call_quantity_label": {"default_value": True},
+ "lottery_method": {"default_value": 1},
+ "show_lottery_name": {"default_value": False},
+ "reset_lottery": {"default_value": True},
+ "lottery_quantity_control": {"default_value": True},
+ "lottery_start_button": {"default_value": True},
+ "lottery_list": {"default_value": True},
+ "lottery_quantity_label": {"default_value": True},
+ "custom_method": {"default_value": 1},
+ "reset_custom": {"default_value": True},
+ "custom_quantity_control": {"default_value": True},
+ "custom_start_button": {"default_value": True},
+ "custom_list": {"default_value": True},
+ "custom_range_start": {"default_value": True},
+ "custom_range_end": {"default_value": True},
+ "draw_custom_method": {"default_value": True},
+ "custom_quantity_label": {"default_value": True},
},
"floating_window_management": {
- "title": {
- "default_value": None
- },
- "basic_settings": {
- "default_value": None
- },
- "appearance_settings": {
- "default_value": None
- },
- "edge_settings": {
- "default_value": None
- },
- "startup_display_floating_window": {
- "default_value": True
- },
- "floating_window_opacity": {
- "default_value": 0.8
- },
- "reset_floating_window_position_button": {
- "default_value": None
- },
- "floating_window_button_control": {
- "default_value": 5
- },
- "floating_window_placement": {
- "default_value": 1
- },
- "floating_window_display_style": {
- "default_value": 0
- },
- "floating_window_stick_to_edge": {
- "default_value": True
- },
- "floating_window_stick_to_edge_recover_seconds": {
- "default_value": 5
- },
- "floating_window_stick_to_edge_display_style": {
- "default_value": 1
- }
- },
- "sidebar_tray_management": {
- "title": {
- "default_value": None
- }
- },
+ "title": {"default_value": None},
+ "basic_settings": {"default_value": None},
+ "appearance_settings": {"default_value": None},
+ "edge_settings": {"default_value": None},
+ "startup_display_floating_window": {"default_value": True},
+ "floating_window_opacity": {"default_value": 0.8},
+ "reset_floating_window_position_button": {"default_value": None},
+ "floating_window_button_control": {"default_value": 5},
+ "floating_window_placement": {"default_value": 1},
+ "floating_window_display_style": {"default_value": 0},
+ "floating_window_stick_to_edge": {"default_value": True},
+ "floating_window_stick_to_edge_recover_seconds": {"default_value": 5},
+ "floating_window_stick_to_edge_display_style": {"default_value": 1},
+ },
+ "sidebar_tray_management": {"title": {"default_value": None}},
"sidebar_management_window": {
- "title": {
- "default_value": None
- },
- "roll_call_sidebar_position": {
- "default_value": 1
- },
- "custom_roll_call_sidebar_position": {
- "default_value": 1
- },
- "lottery_sidebar_position": {
- "default_value": 1
- },
- "main_window_history": {
- "default_value": 1
- },
- "settings_icon": {
- "default_value": 1
- },
+ "title": {"default_value": None},
+ "roll_call_sidebar_position": {"default_value": 1},
+ "custom_roll_call_sidebar_position": {"default_value": 1},
+ "lottery_sidebar_position": {"default_value": 1},
+ "main_window_history": {"default_value": 1},
+ "settings_icon": {"default_value": 1},
},
"sidebar_management_settings": {
- "title": {
- "default_value": None
- },
- "home": {
- "default_value": 0
- },
- "base_settings": {
- "default_value": 0
- },
- "name_management": {
- "default_value": 0
- },
- "draw_settings": {
- "default_value": 0
- },
- "notification_service": {
- "default_value": 1
- },
- "security_settings": {
- "default_value": 1
- },
- "personal_settings": {
- "default_value": 1
- },
- "voice_settings": {
- "default_value": 1
- },
- "settings_history": {
- "default_value": 1
- },
- "more_settings": {
- "default_value": 1
- }
+ "title": {"default_value": None},
+ "home": {"default_value": 0},
+ "base_settings": {"default_value": 0},
+ "name_management": {"default_value": 0},
+ "draw_settings": {"default_value": 0},
+ "notification_service": {"default_value": 1},
+ "security_settings": {"default_value": 1},
+ "personal_settings": {"default_value": 1},
+ "voice_settings": {"default_value": 1},
+ "settings_history": {"default_value": 1},
+ "more_settings": {"default_value": 1},
},
"tray_management": {
- "title": {
- "default_value": None
- },
- "show_hide_main_window": {
- "default_value": True
- },
- "open_settings": {
- "default_value": True
- },
- "show_hide_float_window": {
- "default_value": True
- },
- "restart": {
- "default_value": True
- },
- "exit": {
- "default_value": True
- }
+ "title": {"default_value": None},
+ "show_hide_main_window": {"default_value": True},
+ "open_settings": {"default_value": True},
+ "show_hide_float_window": {"default_value": True},
+ "restart": {"default_value": True},
+ "exit": {"default_value": True},
},
"voice_settings": {
- "title": {
- "default_value": None
- },
+ "title": {"default_value": None},
},
"basic_voice_settings": {
- "title": {
- "default_value": None
- },
- "voice_engine_group": {
- "default_value": None
- },
- "volume_group": {
- "default_value": None
- },
- "system_volume_group": {
- "default_value": None
- },
- "voice_engine": {
- "default_value": 0
- },
- "edge_tts_voice_name": {
- "default_value": "zh-CN-XiaoxiaoNeural"
- },
- "voice_playback": {
- "default_value": 0
- },
- "volume_size": {
- "default_value": 80
- },
- "speech_rate": {
- "default_value": 100
- },
- "system_volume_control": {
- "default_value": 0
- },
- "system_volume_size": {
- "default_value": 80
- }
- },
- "history": {
- "title": {
- "default_value": None
- }
- },
+ "title": {"default_value": None},
+ "voice_engine_group": {"default_value": None},
+ "volume_group": {"default_value": None},
+ "system_volume_group": {"default_value": None},
+ "voice_engine": {"default_value": 0},
+ "edge_tts_voice_name": {"default_value": "zh-CN-XiaoxiaoNeural"},
+ "voice_playback": {"default_value": 0},
+ "volume_size": {"default_value": 80},
+ "speech_rate": {"default_value": 100},
+ "system_volume_control": {"default_value": 0},
+ "system_volume_size": {"default_value": 80},
+ },
+ "history": {"title": {"default_value": None}},
"history_management": {
- "title": {
- "default_value": None
- },
- "roll_call": {
- "default_value": None
- },
- "lottery_history": {
- "default_value": None
- },
- "show_roll_call_history": {
- "default_value": True
- },
- "select_class_name": {
- "default_value": 0
- },
- "clear_roll_call_history": {
- "default_value": None
- },
- "show_lottery_history": {
- "default_value": True
- },
- "select_pool_name": {
- "default_value": 0
- },
- "clear_lottery_history": {
- "default_value": None
- }
+ "title": {"default_value": None},
+ "roll_call": {"default_value": None},
+ "lottery_history": {"default_value": None},
+ "show_roll_call_history": {"default_value": True},
+ "select_class_name": {"default_value": 0},
+ "clear_roll_call_history": {"default_value": None},
+ "show_lottery_history": {"default_value": True},
+ "select_pool_name": {"default_value": 0},
+ "clear_lottery_history": {"default_value": None},
},
"roll_call_history_table": {
- "title": {
- "default_value": None
- },
- "select_class_name": {
- "default_value": 0
- },
- "select_mode": {
- "default_value": None
- },
- "HeaderLabels_all_not_weight": {
- "default_value": None
- },
- "HeaderLabels_all_weight": {
- "default_value": None
- },
- "HeaderLabels_time": {
- "default_value": None
- },
- "HeaderLabels_Individual": {
- "default_value": None
- },
- "select_weight": {
- "default_value": False
- },
+ "title": {"default_value": None},
+ "select_class_name": {"default_value": 0},
+ "select_mode": {"default_value": None},
+ "HeaderLabels_all_not_weight": {"default_value": None},
+ "HeaderLabels_all_weight": {"default_value": None},
+ "HeaderLabels_time": {"default_value": None},
+ "HeaderLabels_Individual": {"default_value": None},
+ "select_weight": {"default_value": False},
},
"more_settings": {
- "title": {
- "default_value": None
- },
+ "title": {"default_value": None},
},
"advanced_settings": {
- "title": {
- "default_value": None
- },
- "fair_draw": {
- "default_value": True
- },
- "fair_draw_group": {
- "default_value": True
- },
- "fair_draw_gender": {
- "default_value": True
- },
- "fair_draw_time": {
- "default_value": True
- },
- "base_weight": {
- "default_value": 1.00
- },
- "min_weight": {
- "default_value": 0.50
- },
- "max_weight": {
- "default_value": 5.00
- },
- },
- "debug":{
- "title": {
- "default_value": None
- }
- },
+ "title": {"default_value": None},
+ "fair_draw": {"default_value": True},
+ "fair_draw_group": {"default_value": True},
+ "fair_draw_gender": {"default_value": True},
+ "fair_draw_time": {"default_value": True},
+ "base_weight": {"default_value": 1.00},
+ "min_weight": {"default_value": 0.50},
+ "max_weight": {"default_value": 5.00},
+ },
+ "debug": {"title": {"default_value": None}},
"about": {
- "title": {
- "default_value": None
- },
- "github": {
- "default_value": None
- },
- "bilibili": {
- "default_value": None
- },
- "contributor": {
- "default_value": None
- },
- "donation": {
- "default_value": None
- },
- "check_update": {
- "default_value": None
- },
- "website": {
- "default_value": None
- },
- "channel": {
- "default_value": 0
- },
- "copyright": {
- "default_value": None
- },
- "version": {
- "default_value": None
- }
- }
+ "title": {"default_value": None},
+ "github": {"default_value": None},
+ "bilibili": {"default_value": None},
+ "contributor": {"default_value": None},
+ "donation": {"default_value": None},
+ "check_update": {"default_value": None},
+ "website": {"default_value": None},
+ "channel": {"default_value": 0},
+ "copyright": {"default_value": None},
+ "version": {"default_value": None},
+ },
}
diff --git a/app/tools/variable.py b/app/tools/variable.py
index 9d0330de..b47a2727 100644
--- a/app/tools/variable.py
+++ b/app/tools/variable.py
@@ -1,88 +1,149 @@
# 定义非设置项的初始变量
# ==================== 软件基本信息 ====================
-APPLY_NAME = "SecRandom" # 软件名称
-VERSION = "v0.0.0.0" # 软件当前版本
-NEXT_VERSION = "v2.0.0.0" # 软件下一个版本
-YEAR = 2025 # 软件发布年份
-MONTH = 4 # 软件发布月份
-AUTHOR = "lzy98276" # 软件作者
-APP_DESCRIPTION = "一个易用的班级抽号软件,专为教育场景设计,让课堂点名更高效透明" # 软件描述
+APPLY_NAME = "SecRandom" # 软件名称
+VERSION = "v0.0.0.0" # 软件当前版本
+NEXT_VERSION = "v2.0.0.0" # 软件下一个版本
+YEAR = 2025 # 软件发布年份
+MONTH = 4 # 软件发布月份
+AUTHOR = "lzy98276" # 软件作者
+APP_DESCRIPTION = (
+ "一个易用的班级抽号软件,专为教育场景设计,让课堂点名更高效透明" # 软件描述
+)
APP_COPYRIGHT = f"Copyright © {YEAR} {AUTHOR}. All rights reserved." # 软件版权信息
-APP_LICENSE = "GPL-3.0 License" # 软件许可证
-APP_EMAIL = "lzy.12@foxmail.com" # 软件作者邮箱
+APP_LICENSE = "GPL-3.0 License" # 软件许可证
+APP_EMAIL = "lzy.12@foxmail.com" # 软件作者邮箱
# ==================== 联系与链接信息 ====================
-GITHUB_WEB = "https://github.com/SECTL/SecRandom" # 软件GitHub仓库
+GITHUB_WEB = "https://github.com/SECTL/SecRandom" # 软件GitHub仓库
BILIBILI_WEB = "https://space.bilibili.com/520571577" # 软件作者Bilibili空间
-WEBSITE = "https://secrandom.netlify.app" # 软件官方网站
-DONATION_URL = "https://afdian.com/a/lzy0983" # 捐赠链接
+WEBSITE = "https://secrandom.netlify.app" # 软件官方网站
+DONATION_URL = "https://afdian.com/a/lzy0983" # 捐赠链接
# ==================== UI相关默认值 ====================
-DEFAULT_THEME_COLOR = "#66CCFF" # 天依蓝 - 默认主题色
-FALLBACK_THEME_COLOR = "#3AF2FF" # 备用主题色
-DEFAULT_ICON_CODEPOINT = 62634 # 默认图标码点(info图标)
-WIDTH_SPINBOX = 180 # 自旋框宽度
-MINIMUM_WINDOW_SIZE = (600, 400) # 窗口最小尺寸
+DEFAULT_THEME_COLOR = "#66CCFF" # 天依蓝 - 默认主题色
+FALLBACK_THEME_COLOR = "#3AF2FF" # 备用主题色
+DEFAULT_ICON_CODEPOINT = 62634 # 默认图标码点(info图标)
+WIDTH_SPINBOX = 180 # 自旋框宽度
+MINIMUM_WINDOW_SIZE = (600, 400) # 窗口最小尺寸
# ==================== 字体相关配置 ====================
# 第一种字体名称
-DEFAULT_FONT_NAME_PRIMARY = "HarmonyOS Sans SC" # 第一种字体名称
+DEFAULT_FONT_NAME_PRIMARY = "HarmonyOS Sans SC" # 第一种字体名称
DEFAULT_FONT_FILENAME_PRIMARY = "HarmonyOS_Sans_SC_Bold.ttf" # 第一种字体文件名
# 第二种字体名称
-DEFAULT_FONT_NAME_ALT = "汉仪文黑-85W" # 第二种字体名称
-DEFAULT_FONT_FILENAME_ALT = "汉仪文黑-85W.ttf" # 第二种字体文件名
+DEFAULT_FONT_NAME_ALT = "汉仪文黑-85W" # 第二种字体名称
+DEFAULT_FONT_FILENAME_ALT = "汉仪文黑-85W.ttf" # 第二种字体文件名
# 字体配置映射表 - 提高可维护性
FONT_CONFIG_MAP = {
DEFAULT_FONT_NAME_ALT: {
- 'filename': DEFAULT_FONT_FILENAME_ALT,
- 'display_name': DEFAULT_FONT_NAME_ALT
+ "filename": DEFAULT_FONT_FILENAME_ALT,
+ "display_name": DEFAULT_FONT_NAME_ALT,
},
DEFAULT_FONT_NAME_PRIMARY: {
- 'filename': DEFAULT_FONT_FILENAME_PRIMARY,
- 'display_name': DEFAULT_FONT_NAME_PRIMARY
- }
+ "filename": DEFAULT_FONT_FILENAME_PRIMARY,
+ "display_name": DEFAULT_FONT_NAME_PRIMARY,
+ },
}
# ==================== 文件系统相关默认值 ====================
-DEFAULT_SETTINGS_FILENAME = "settings.json" # 默认设置文件名
-DEFAULT_FILE_ENCODING = "utf-8" # 默认文件编码
+DEFAULT_SETTINGS_FILENAME = "settings.json" # 默认设置文件名
+DEFAULT_FILE_ENCODING = "utf-8" # 默认文件编码
# ==================== 路径相关常量 ====================
# 日志路径
-LOG_DIR = "logs" # 日志目录名
+LOG_DIR = "logs" # 日志目录名
LOG_FILENAME_FORMAT = "SecRandom_{time:YYYY-MM-DD-HH-mm-ss}.log" # 日志文件名格式
# 资源文件夹路径
STUDENT_IMAGE_FOLDER = "images/student_images" # 学生图片文件夹名
-PRIZE_IMAGE_FOLDER = "images/prize_images" # 奖品图片文件夹名
+PRIZE_IMAGE_FOLDER = "images/prize_images" # 奖品图片文件夹名
ANIMATION_MUSIC_FOLDER = "music/animation_music" # 动画音乐文件夹名
-RESULT_MUSIC_FOLDER = "music/result_music" # 结果音乐文件夹名
+RESULT_MUSIC_FOLDER = "music/result_music" # 结果音乐文件夹名
# ==================== 日志相关常量 ====================
-LOG_ROTATION_SIZE = "1 MB" # 日志文件轮转大小
-LOG_RETENTION_DAYS = "30 days" # 日志保留天数
-LOG_COMPRESSION = "tar.gz" # 日志压缩格式
+LOG_ROTATION_SIZE = "1 MB" # 日志文件轮转大小
+LOG_RETENTION_DAYS = "30 days" # 日志保留天数
+LOG_COMPRESSION = "tar.gz" # 日志压缩格式
# ==================== 应用程序相关常量 ====================
-APP_QUIT_ON_LAST_WINDOW_CLOSED = False # 最后一个窗口关闭时是否退出应用程序
-APP_INIT_DELAY = 100 # 应用程序初始化延迟时间(毫秒)
-FONT_APPLY_DELAY = 0 # 字体应用延迟时间(毫秒)
-APPLY_DELAY = 0 # 应用延迟时间(毫秒)
+APP_QUIT_ON_LAST_WINDOW_CLOSED = False # 最后一个窗口关闭时是否退出应用程序
+APP_INIT_DELAY = 100 # 应用程序初始化延迟时间(毫秒)
+FONT_APPLY_DELAY = 0 # 字体应用延迟时间(毫秒)
+APPLY_DELAY = 0 # 应用延迟时间(毫秒)
# ==================== 界面交互相关常量 ====================
-MENU_AUTO_CLOSE_TIMEOUT = 5000 # 菜单自动关闭时间(毫秒)
+MENU_AUTO_CLOSE_TIMEOUT = 5000 # 菜单自动关闭时间(毫秒)
# ==================== 语言相关常量 ====================
-LANGUAGE_ZH_CN = "ZH_CN" # 中文
-LANGUAGE_EN_US = "EN_US" # 英文
-DEFAULT_LANGUAGE = LANGUAGE_ZH_CN # 默认语言为中文
-LANGUAGE_MODULE_DIR= "app/Language/modules" # 模块化语言文件路径
+LANGUAGE_ZH_CN = "ZH_CN" # 中文
+LANGUAGE_EN_US = "EN_US" # 英文
+DEFAULT_LANGUAGE = LANGUAGE_ZH_CN # 默认语言为中文
+LANGUAGE_MODULE_DIR = "app/Language/modules" # 模块化语言文件路径
# ==================== 共享内存相关常量 ====================
-SHARED_MEMORY_KEY = "SecRandomSharedMemory" # 共享内存键名
+SHARED_MEMORY_KEY = "SecRandomSharedMemory" # 共享内存键名
+
+# ==================== 结果显示相关常量 ====================
+# 布局间距
+AVATAR_LABEL_SPACING = 8 # 头像和标签之间的间距
+GRID_LAYOUT_SPACING = 20 # 网格布局统一间距
+GRID_HORIZONTAL_SPACING = 20 # 网格布局水平间距
+GRID_VERTICAL_SPACING = 10 # 网格布局垂直间距
+GRID_ITEM_MARGIN = 40 # 网格项目边距
+GRID_ITEM_SPACING = 20 # 网格项目间距
+DEFAULT_AVAILABLE_WIDTH = 800 # 默认可用宽度
+
+# 颜色生成相关
+DEFAULT_MIN_SATURATION = 0.7 # 默认最小饱和度
+DEFAULT_MAX_SATURATION = 1.0 # 默认最大饱和度
+DEFAULT_MIN_VALUE = 0.7 # 默认最小亮度值
+DEFAULT_MAX_VALUE = 1.0 # 默认最大亮度值
+
+# 浅色主题颜色调整
+LIGHT_VALUE_MULTIPLIER = 0.7 # 浅色主题亮度乘数
+LIGHT_MAX_VALUE_MULTIPLIER = 0.8 # 浅色主题最大亮度乘数
+LIGHT_THEME_MAX_VALUE = 0.5 # 浅色主题最大亮度值
+LIGHT_THEME_ADJUSTED_MAX_VALUE = 0.7 # 浅色主题调整后的最大亮度值
+
+# 深色主题颜色调整
+DARK_VALUE_MULTIPLIER = 1.2 # 深色主题亮度乘数
+DARK_MAX_VALUE_MULTIPLIER = 1.0 # 深色主题最大亮度乘数
+DARK_THEME_MIN_VALUE = 0.85 # 深色主题最小亮度值
+DARK_THEME_MAX_VALUE = 1.0 # 深色主题最大亮度值
+LIGHTNESS_THRESHOLD = 127 # 浅色/深色主题亮度阈值
+RGB_COLOR_FORMAT = "rgb({r},{g},{b})" # RGB颜色格式字符串
+
+# 图片相关
+SUPPORTED_IMAGE_EXTENSIONS = [".png", ".jpg", ".jpeg", ".svg"] # 支持的图片扩展名
+
+# 格式化相关
+STUDENT_ID_FORMAT = "{num:02}" # 学号格式化字符串
+NAME_SPACING = " " # 姓名之间的间距
+
+# ==================== 贡献者页面相关常量 ====================
+CONTRIBUTOR_CARD_MIN_WIDTH = 250 # 贡献者卡片最小宽度
+CONTRIBUTOR_MAX_COLUMNS = 12 # 贡献者页面最大列数限制
+CONTRIBUTOR_CARD_SPACING = 20 # 贡献者卡片间距
+CONTRIBUTOR_CARD_MARGIN = 15 # 贡献者卡片内边距
+CONTRIBUTOR_AVATAR_RADIUS = 64 # 贡献者头像半径
+CONTRIBUTOR_MAX_ROLE_WIDTH = 500 # 贡献者职责文本最大宽度
+
+# ==================== 学生卡片相关常量 ====================
+STUDENT_CARD_MIN_WIDTH = 200 # 学生卡片最小宽度
+STUDENT_CARD_FIXED_WIDTH = 180 # 学生卡片固定宽度
+STUDENT_CARD_FIXED_HEIGHT = 100 # 学生卡片固定高度
+STUDENT_MAX_COLUMNS = 8 # 学生页面最大列数限制
+STUDENT_CARD_SPACING = 15 # 学生卡片间距
+STUDENT_CARD_MARGIN = 12 # 学生卡片内边距
+STUDENT_AVATAR_RADIUS = 50 # 学生头像半径
# ==================== 全局变量 ====================
-main_window = None # 全局主窗口引用
\ No newline at end of file
+main_window = None # 全局主窗口引用
+
+# ==================== Settings 背景预热配置 ====================
+# 后台预热设置页面的默认时间间隔(毫秒)和默认最大预热页数
+SETTINGS_WARMUP_INTERVAL_MS = 800
+SETTINGS_WARMUP_MAX_PRELOAD = 1
diff --git a/app/view/__init__.py b/app/view/__init__.py
new file mode 100644
index 00000000..1af43355
--- /dev/null
+++ b/app/view/__init__.py
@@ -0,0 +1 @@
+"""View layer package for SecRandom."""
diff --git a/app/view/another_window/__init__.py b/app/view/another_window/__init__.py
new file mode 100644
index 00000000..9186a5b1
--- /dev/null
+++ b/app/view/another_window/__init__.py
@@ -0,0 +1 @@
+"""Supplementary windows package."""
diff --git a/app/view/another_window/contributor.py b/app/view/another_window/contributor.py
new file mode 100644
index 00000000..b1eac079
--- /dev/null
+++ b/app/view/another_window/contributor.py
@@ -0,0 +1,313 @@
+# ==================================================
+# 导入库
+# ==================================================
+
+from loguru import logger
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from qfluentwidgets import *
+
+from app.tools.variable import *
+from app.tools.path_utils import *
+from app.tools.personalised import *
+from app.tools.settings_default import *
+from app.tools.settings_access import *
+from app.Language.obtain_language import *
+
+
+# ==================================================
+# 贡献者页面类
+# ==================================================
+class contributor_page(QWidget):
+ """贡献者信息页面 - 显示项目贡献者信息,采用响应式网格布局"""
+
+ def __init__(self, parent=None):
+ """初始化贡献者页面"""
+ super().__init__(parent)
+ self.setObjectName("contributor_page")
+
+ # 初始化UI组件
+ self._init_ui()
+
+ # 初始化数据
+ self._init_data()
+
+ # 延迟添加贡献者卡片
+ QTimer.singleShot(100, self.create_contributor_cards)
+
+ def _init_ui(self):
+ """初始化UI组件"""
+ # 创建主布局
+ self.main_layout = QVBoxLayout(self)
+ self.main_layout.setContentsMargins(10, 10, 10, 10)
+ self.main_layout.setSpacing(10)
+
+ # 创建网格布局
+ self.grid_layout = QGridLayout()
+ self.grid_layout.setSpacing(CONTRIBUTOR_CARD_SPACING)
+ self.main_layout.addLayout(self.grid_layout)
+
+ # 初始化卡片列表
+ self.cards = []
+
+ def _init_data(self):
+ """初始化贡献者数据"""
+ # 贡献者数据
+ contributors = [
+ {
+ "name": "lzy98276",
+ "role": get_any_position_value_async(
+ "about", "contributor", "contributor_role_1"
+ ),
+ "github": "https://github.com/lzy98276",
+ "avatar": str(
+ get_resources_path("assets/contribution", "contributor1.png")
+ ),
+ },
+ {
+ "name": "QiKeZhiCao",
+ "role": get_any_position_value_async(
+ "about", "contributor", "contributor_role_2"
+ ),
+ "github": "https://github.com/QiKeZhiCao",
+ "avatar": str(
+ get_resources_path("assets/contribution", "contributor2.png")
+ ),
+ },
+ {
+ "name": "Fox-block-offcial",
+ "role": get_any_position_value_async(
+ "about", "contributor", "contributor_role_3"
+ ),
+ "github": "https://github.com/Fox-block-offcial",
+ "avatar": str(
+ get_resources_path("assets/contribution", "contributor3.png")
+ ),
+ },
+ {
+ "name": "yuanbenxin",
+ "role": get_any_position_value_async(
+ "about", "contributor", "contributor_role_4"
+ ),
+ "github": "https://github.com/yuanbenxin",
+ "avatar": str(
+ get_resources_path("assets/contribution", "contributor4.png")
+ ),
+ },
+ {
+ "name": "LeafS",
+ "role": get_any_position_value_async(
+ "about", "contributor", "contributor_role_5"
+ ),
+ "github": "https://github.com/LeafS825",
+ "avatar": str(
+ get_resources_path("assets/contribution", "contributor5.png")
+ ),
+ },
+ {
+ "name": "Jursin",
+ "role": get_any_position_value_async(
+ "about", "contributor", "contributor_role_6"
+ ),
+ "github": "https://github.com/jursin",
+ "avatar": str(
+ get_resources_path("assets/contribution", "contributor6.png")
+ ),
+ },
+ {
+ "name": "LHGS-github",
+ "role": get_any_position_value_async(
+ "about", "contributor", "contributor_role_7"
+ ),
+ "github": "https://github.com/LHGS-github",
+ "avatar": str(
+ get_resources_path("assets/contribution", "contributor7.png")
+ ),
+ },
+ {
+ "name": "real01bit",
+ "role": get_any_position_value_async(
+ "about", "contributor", "contributor_role_8"
+ ),
+ "github": "https://github.com/real01bit",
+ "avatar": str(
+ get_resources_path("assets/contribution", "contributor8.png")
+ ),
+ },
+ ]
+
+ # 标准化职责文本长度
+ self._standardize_role_text(contributors)
+ self.contributors = contributors
+
+ def _standardize_role_text(self, contributors):
+ """标准化职责文本长度,使所有职责文本行数一致"""
+ # 计算所有职责文本的行数
+ fm = QFontMetrics(self.font())
+ max_lines = 0
+ role_lines = []
+
+ # 找出最长的职责文本有多少行
+ for contributor in contributors:
+ role_text = contributor["role"] or "" # 确保role_text不为None
+ contributor["role"] = role_text
+
+ # 计算文本在MAX_ROLE_WIDTH宽度下的行数
+ text_rect = fm.boundingRect(
+ QRect(0, 0, CONTRIBUTOR_MAX_ROLE_WIDTH, 0),
+ Qt.TextFlag.TextWordWrap,
+ role_text,
+ )
+ line_count = text_rect.height() // fm.lineSpacing()
+ role_lines.append(line_count)
+ max_lines = max(max_lines, line_count)
+
+ # 为每个职责文本添加换行符,确保行数相同
+ for i, contributor in enumerate(contributors):
+ current_lines = role_lines[i]
+ if current_lines < max_lines:
+ contributor["role"] += "\n" * (max_lines - current_lines)
+
+ def create_contributor_cards(self):
+ """创建贡献者卡片"""
+ if not hasattr(self, "grid_layout") or self.grid_layout is None:
+ return
+
+ # 添加贡献者卡片
+ for contributor in self.contributors:
+ card = self.addContributorCard(contributor)
+ if card is not None: # 只添加有效的卡片
+ self.cards.append(card)
+
+ # 延迟更新布局
+ QTimer.singleShot(50, self.update_layout)
+
+ def update_layout(self):
+ """更新布局 - 根据窗口大小动态调整卡片排列"""
+ # 清空网格布局
+ self._clear_grid_layout()
+
+ def calculate_columns(width):
+ """根据窗口宽度和卡片尺寸动态计算列数"""
+ if width <= 0:
+ return 1
+
+ # 计算可用宽度(减去左右边距)
+ available_width = width - 40 # 左右各20px边距
+
+ # 计算单个卡片实际占用的宽度(包括间距)
+ card_actual_width = CONTRIBUTOR_CARD_MIN_WIDTH + CONTRIBUTOR_CARD_SPACING
+
+ # 计算最大可能列数(不超过MAX_COLUMNS)
+ cols = min(available_width // card_actual_width, CONTRIBUTOR_MAX_COLUMNS)
+
+ # 至少显示1列
+ return max(cols, 1)
+
+ # 获取窗口实际可用宽度
+ window_width = max(self.width(), self.sizeHint().width())
+
+ # 根据窗口宽度计算列数
+ cols = calculate_columns(window_width)
+
+ # 设置网格布局的列伸缩因子,使卡片均匀分布
+ for col in range(cols):
+ self.grid_layout.setColumnStretch(col, 1)
+
+ # 添加卡片到网格
+ for i, card in enumerate(self.cards):
+ row = i // cols
+ col = i % cols
+ # 设置卡片的最小宽度和最大宽度
+ card.setMinimumWidth(CONTRIBUTOR_CARD_MIN_WIDTH)
+ card.setMaximumWidth(
+ CONTRIBUTOR_CARD_MIN_WIDTH * 1.5
+ ) # 设置最大宽度,防止卡片过宽
+ self.grid_layout.addWidget(card, row, col, Qt.AlignmentFlag.AlignCenter)
+ card.show()
+
+ def _clear_grid_layout(self):
+ """清空网格布局"""
+ # 重置列伸缩因子
+ for col in range(self.grid_layout.columnCount()):
+ self.grid_layout.setColumnStretch(col, 0)
+
+ while self.grid_layout.count():
+ item = self.grid_layout.takeAt(0)
+ widget = item.widget()
+ if widget:
+ widget.hide()
+ widget.setParent(None)
+
+ def addContributorCard(self, contributor):
+ """添加单个贡献者卡片"""
+ if not hasattr(self, "grid_layout") or self.grid_layout is None:
+ return None
+
+ try:
+ card = QWidget()
+ card.setObjectName("contributorCard")
+ cardLayout = QVBoxLayout(card)
+ cardLayout.setContentsMargins(
+ CONTRIBUTOR_CARD_MARGIN,
+ CONTRIBUTOR_CARD_MARGIN,
+ CONTRIBUTOR_CARD_MARGIN,
+ CONTRIBUTOR_CARD_MARGIN,
+ )
+ cardLayout.setSpacing(10)
+
+ # 头像
+ avatar = AvatarWidget(contributor["avatar"])
+ avatar.setRadius(CONTRIBUTOR_AVATAR_RADIUS)
+ avatar.setAlignment(Qt.AlignmentFlag.AlignCenter)
+ cardLayout.addWidget(avatar, 0, Qt.AlignmentFlag.AlignCenter)
+
+ # 昵称作为GitHub链接
+ name = HyperlinkButton(contributor["github"], contributor["name"], None)
+ name.setStyleSheet(
+ "text-decoration: underline; color: #0066cc; background: transparent; border: none; padding: 0;"
+ )
+ cardLayout.addWidget(name, 0, Qt.AlignmentFlag.AlignCenter)
+
+ # 职责
+ role_text = contributor["role"] or ""
+ role = BodyLabel(role_text)
+ role.setAlignment(Qt.AlignmentFlag.AlignCenter)
+ role.setWordWrap(True)
+ role.setMaximumWidth(CONTRIBUTOR_MAX_ROLE_WIDTH)
+ cardLayout.addWidget(role, 0, Qt.AlignmentFlag.AlignCenter)
+
+ return card
+ except RuntimeError as e:
+ logger.error(f"创建贡献者卡片时出错: {e}")
+ return None
+
+ def resizeEvent(self, event):
+ """窗口大小变化事件"""
+ # 使用QTimer延迟布局更新,避免递归调用
+ if hasattr(self, "_resize_timer") and self._resize_timer is not None:
+ self._resize_timer.stop()
+ self._resize_timer = QTimer()
+ self._resize_timer.setSingleShot(True)
+ self._resize_timer.timeout.connect(self._delayed_update_layout)
+ self._resize_timer.start(50)
+ super().resizeEvent(event)
+
+ def _delayed_update_layout(self):
+ """延迟更新布局"""
+ try:
+ if hasattr(self, "grid_layout") and self.grid_layout is not None:
+ if self.isVisible():
+ self.update_layout()
+ except RuntimeError as e:
+ logger.error(f"延迟布局更新错误: {e}")
+
+ def closeEvent(self, event):
+ """窗口关闭事件"""
+ # 清理定时器
+ if hasattr(self, "_resize_timer") and self._resize_timer is not None:
+ self._resize_timer.stop()
+ self._resize_timer = None
+
+ super().closeEvent(event)
diff --git a/app/view/another_window/gender_setting.py b/app/view/another_window/gender_setting.py
new file mode 100644
index 00000000..2846bfca
--- /dev/null
+++ b/app/view/another_window/gender_setting.py
@@ -0,0 +1,347 @@
+# ==================================================
+# 导入库
+# ==================================================
+import re
+import json
+
+from loguru import logger
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from qfluentwidgets import *
+
+from app.tools.variable import *
+from app.tools.path_utils import *
+from app.tools.personalised import *
+from app.tools.settings_default import *
+from app.tools.settings_access import *
+from app.Language.obtain_language import *
+from app.tools.config import *
+from app.tools.list import *
+
+
+class GenderSettingWindow(QWidget):
+ """性别设置窗口"""
+
+ def __init__(self, parent=None):
+ """初始化性别设置窗口"""
+ super().__init__(parent)
+
+ # 初始化变量
+ self.saved = False
+ self.initial_genders = [] # 保存初始加载的性别列表
+
+ # 初始化UI
+ self.init_ui()
+
+ # 连接信号
+ self.__connect_signals()
+
+ def init_ui(self):
+ """初始化UI"""
+ # 设置窗口标题
+ self.setWindowTitle(get_content_name_async("gender_setting", "title"))
+
+ # 创建主布局
+ self.main_layout = QVBoxLayout(self)
+ self.main_layout.setContentsMargins(20, 20, 20, 20)
+ self.main_layout.setSpacing(15)
+
+ # 创建标题
+ self.title_label = TitleLabel(get_content_name_async("gender_setting", "title"))
+ self.main_layout.addWidget(self.title_label)
+
+ # 创建说明标签
+ self.description_label = BodyLabel(
+ get_content_name_async("gender_setting", "description")
+ )
+ self.description_label.setWordWrap(True)
+ self.main_layout.addWidget(self.description_label)
+
+ # 创建性别输入区域
+ self.__create_gender_input_area()
+
+ # 创建按钮区域
+ self.__create_button_area()
+
+ # 添加伸缩项
+ self.main_layout.addStretch(1)
+
+ def __create_gender_input_area(self):
+ """创建性别输入区域"""
+ # 创建卡片容器
+ input_card = CardWidget()
+ input_layout = QVBoxLayout(input_card)
+
+ # 创建输入区域标题
+ input_title = SubtitleLabel(
+ get_content_name_async("gender_setting", "input_title")
+ )
+ input_layout.addWidget(input_title)
+
+ # 创建文本编辑框
+ self.text_edit = PlainTextEdit()
+ self.text_edit.setPlaceholderText(
+ get_content_name_async("gender_setting", "input_placeholder")
+ )
+
+ # 加载现有性别
+ existing_genders = self.__load_existing_genders()
+ if existing_genders:
+ self.text_edit.setPlainText("\n".join(existing_genders))
+
+ input_layout.addWidget(self.text_edit)
+
+ # 添加到主布局
+ self.main_layout.addWidget(input_card)
+
+ def __load_existing_genders(self):
+ """加载现有性别"""
+ try:
+ # 获取班级名单目录
+ roll_call_list_dir = get_path("app/resources/list/roll_call_list")
+
+ # 从设置中获取班级名称
+ class_name = readme_settings_async("roll_call_list", "select_class_name")
+ list_file = roll_call_list_dir / f"{class_name}.json"
+
+ # 如果文件不存在,返回空列表
+ if not list_file.exists():
+ self.initial_genders = []
+ return []
+
+ # 读取文件内容
+ with open_file(list_file, "r", encoding="utf-8") as f:
+ data = json.load(f)
+
+ # 获取所有性别(从每个学生的gender字段)
+ genders = []
+ for student_name, student_info in data.items():
+ if "gender" in student_info and student_info["gender"]:
+ genders.append(student_info["gender"])
+
+ self.initial_genders = genders.copy()
+
+ return genders
+
+ except Exception as e:
+ logger.error(f"加载性别失败: {str(e)}")
+ self.initial_genders = []
+ return []
+
+ def __create_button_area(self):
+ """创建按钮区域"""
+ # 创建按钮布局
+ button_layout = QHBoxLayout()
+
+ # 伸缩项
+ button_layout.addStretch(1)
+
+ # 保存按钮
+ self.save_button = PrimaryPushButton(
+ get_content_name_async("gender_setting", "save_button")
+ )
+ self.save_button.setIcon(FluentIcon.SAVE)
+ button_layout.addWidget(self.save_button)
+
+ # 取消按钮
+ self.cancel_button = PushButton(
+ get_content_name_async("gender_setting", "cancel_button")
+ )
+ self.cancel_button.setIcon(FluentIcon.CANCEL)
+ button_layout.addWidget(self.cancel_button)
+
+ # 添加到主布局
+ self.main_layout.addLayout(button_layout)
+
+ def __connect_signals(self):
+ """连接信号与槽"""
+ self.save_button.clicked.connect(self.__save_genders)
+ self.cancel_button.clicked.connect(self.__cancel)
+ # 添加文本变化监听器
+ self.text_edit.textChanged.connect(self.__on_text_changed)
+
+ def __on_text_changed(self):
+ """文本变化事件处理"""
+ # 获取当前文本中的性别
+ current_text = self.text_edit.toPlainText()
+ current_genders = [
+ gender.strip() for gender in current_text.split("\n") if gender.strip()
+ ]
+
+ # 检查哪些初始性别被删除了
+ deleted_genders = [
+ gender for gender in self.initial_genders if gender not in current_genders
+ ]
+
+ # 如果有性别被删除,显示提示
+ if deleted_genders:
+ for gender in deleted_genders:
+ # 显示删除提示
+ config = NotificationConfig(
+ title=get_content_name_async(
+ "gender_setting", "gender_deleted_title"
+ ),
+ content=get_content_name_async(
+ "gender_setting", "gender_deleted_message"
+ ).format(gender=gender),
+ duration=3000,
+ )
+ show_notification(NotificationType.INFO, config, parent=self)
+
+ # 更新初始性别列表
+ self.initial_genders = current_genders.copy()
+
+ def __save_genders(self):
+ """保存性别"""
+ try:
+ # 获取输入的性别
+ genders_text = self.text_edit.toPlainText().strip()
+ if not genders_text:
+ # 显示错误消息
+ config = NotificationConfig(
+ title=get_content_name_async("gender_setting", "error_title"),
+ content=get_content_name_async(
+ "gender_setting", "no_genders_error"
+ ),
+ duration=3000,
+ )
+ show_notification(NotificationType.ERROR, config, parent=self)
+ return
+
+ # 分割性别
+ genders = [
+ gender.strip() for gender in genders_text.split("\n") if gender.strip()
+ ]
+
+ # 验证性别
+ invalid_genders = []
+ for gender in genders:
+ # 检查是否包含非法字符
+ if re.search(r'[\/:*?"<>|]', gender):
+ invalid_genders.append(gender)
+ # 检查是否为保留字
+ elif gender.lower() == "class":
+ invalid_genders.append(gender)
+
+ if invalid_genders:
+ # 显示错误消息
+ config = NotificationConfig(
+ title=get_content_name_async("gender_setting", "error_title"),
+ content=get_content_name_async(
+ "gender_setting", "invalid_genders_error"
+ ).format(genders=", ".join(invalid_genders)),
+ duration=5000,
+ )
+ show_notification(NotificationType.ERROR, config, parent=self)
+ return
+
+ # 获取文件路径
+ roll_call_list_dir = get_path("app/resources/list/roll_call_list")
+ roll_call_list_dir.mkdir(parents=True, exist_ok=True)
+
+ # 从设置中获取班级名称
+ class_name = readme_settings_async("roll_call_list", "select_class_name")
+ list_file = roll_call_list_dir / f"{class_name}.json"
+
+ # 读取现有数据
+ existing_data = {}
+ if list_file.exists():
+ with open_file(list_file, "r", encoding="utf-8") as f:
+ existing_data = json.load(f)
+
+ # 如果性别没有新增
+ existing_genders = []
+ for student_name, student_info in existing_data.items():
+ if "gender" in student_info and student_info["gender"]:
+ existing_genders.append(student_info["gender"])
+
+ if set(genders) == set(existing_genders):
+ # 显示提示消息
+ config = NotificationConfig(
+ title=get_content_name_async("gender_setting", "info_title"),
+ content=get_content_name_async(
+ "gender_setting", "no_new_genders_message"
+ ),
+ duration=3000,
+ )
+ show_notification(NotificationType.INFO, config, parent=self)
+ return
+
+ # 更新现有数据中的性别信息
+ updated_data = existing_data.copy()
+
+ # 为每个学生更新性别信息
+ for student_name in updated_data:
+ # 如果学生没有性别字段,则添加空字段
+ if "gender" not in updated_data[student_name]:
+ updated_data[student_name]["gender"] = ""
+
+ # 保存到文件
+ with open_file(list_file, "w", encoding="utf-8") as f:
+ json.dump(updated_data, f, ensure_ascii=False, indent=4)
+
+ # 显示保存成功通知
+ config = NotificationConfig(
+ title=get_content_name_async("gender_setting", "success_title"),
+ content=get_content_name_async(
+ "gender_setting", "success_message"
+ ).format(count=len(genders)),
+ duration=3000,
+ )
+ show_notification(NotificationType.SUCCESS, config, parent=self)
+
+ # 更新初始性别列表
+ self.initial_genders = genders.copy()
+
+ # 标记为已保存
+ self.saved = True
+
+ except Exception as e:
+ # 显示错误消息
+ config = NotificationConfig(
+ title=get_content_name_async("gender_setting", "error_title"),
+ content=f"{get_content_name_async('gender_setting', 'save_error')}: {str(e)}",
+ duration=3000,
+ )
+ show_notification(NotificationType.ERROR, config, parent=self)
+ logger.error(f"保存性别失败: {e}")
+
+ def __cancel(self):
+ """取消操作"""
+ # 获取父窗口并关闭
+ parent = self.parent()
+ while parent:
+ # 查找SimpleWindowTemplate类型的父窗口
+ if hasattr(parent, "windowClosed") and hasattr(parent, "close"):
+ parent.close()
+ break
+ parent = parent.parent()
+
+ def closeEvent(self, event):
+ """窗口关闭事件处理"""
+ if not self.saved:
+ # 创建确认对话框
+ dialog = Dialog(
+ get_content_name_async("gender_setting", "unsaved_changes_title"),
+ get_content_name_async("gender_setting", "unsaved_changes_message"),
+ self,
+ )
+
+ dialog.yesButton.setText(
+ get_content_name_async("gender_setting", "discard_button")
+ )
+ dialog.cancelButton.setText(
+ get_content_name_async("gender_setting", "continue_editing_button")
+ )
+
+ # 显示对话框并获取用户选择
+ if dialog.exec():
+ # 用户选择放弃更改,关闭窗口
+ event.accept()
+ else:
+ # 用户选择继续编辑,取消关闭事件
+ event.ignore()
+ else:
+ # 已保存,直接关闭
+ event.accept()
diff --git a/app/view/another_window/group_setting.py b/app/view/another_window/group_setting.py
new file mode 100644
index 00000000..b7149f75
--- /dev/null
+++ b/app/view/another_window/group_setting.py
@@ -0,0 +1,345 @@
+# ==================================================
+# 导入库
+# ==================================================
+import re
+import json
+
+from loguru import logger
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from qfluentwidgets import *
+
+from app.tools.variable import *
+from app.tools.path_utils import *
+from app.tools.personalised import *
+from app.tools.settings_default import *
+from app.tools.settings_access import *
+from app.Language.obtain_language import *
+from app.tools.config import *
+from app.tools.list import *
+
+
+class GroupSettingWindow(QWidget):
+ """小组设置窗口"""
+
+ def __init__(self, parent=None):
+ """初始化小组设置窗口"""
+ super().__init__(parent)
+
+ # 初始化变量
+ self.saved = False
+ self.initial_groups = [] # 保存初始加载的小组列表
+
+ # 初始化UI
+ self.init_ui()
+
+ # 连接信号
+ self.__connect_signals()
+
+ def init_ui(self):
+ """初始化UI"""
+ # 设置窗口标题
+ self.setWindowTitle(get_content_name_async("group_setting", "title"))
+
+ # 创建主布局
+ self.main_layout = QVBoxLayout(self)
+ self.main_layout.setContentsMargins(20, 20, 20, 20)
+ self.main_layout.setSpacing(15)
+
+ # 创建标题
+ self.title_label = TitleLabel(get_content_name_async("group_setting", "title"))
+ self.main_layout.addWidget(self.title_label)
+
+ # 创建说明标签
+ self.description_label = BodyLabel(
+ get_content_name_async("group_setting", "description")
+ )
+ self.description_label.setWordWrap(True)
+ self.main_layout.addWidget(self.description_label)
+
+ # 创建小组输入区域
+ self.__create_group_input_area()
+
+ # 创建按钮区域
+ self.__create_button_area()
+
+ # 添加伸缩项
+ self.main_layout.addStretch(1)
+
+ def __create_group_input_area(self):
+ """创建小组输入区域"""
+ # 创建卡片容器
+ input_card = CardWidget()
+ input_layout = QVBoxLayout(input_card)
+
+ # 创建输入区域标题
+ input_title = SubtitleLabel(
+ get_content_name_async("group_setting", "input_title")
+ )
+ input_layout.addWidget(input_title)
+
+ # 创建文本编辑框
+ self.text_edit = PlainTextEdit()
+ self.text_edit.setPlaceholderText(
+ get_content_name_async("group_setting", "input_placeholder")
+ )
+
+ # 加载现有小组
+ existing_groups = self.__load_existing_groups()
+ if existing_groups:
+ self.text_edit.setPlainText("\n".join(existing_groups))
+
+ input_layout.addWidget(self.text_edit)
+
+ # 添加到主布局
+ self.main_layout.addWidget(input_card)
+
+ def __load_existing_groups(self):
+ """加载现有小组"""
+ try:
+ # 获取班级名单目录
+ roll_call_list_dir = get_path("app/resources/list/roll_call_list")
+
+ # 从设置中获取班级名称
+ class_name = readme_settings_async("roll_call_list", "select_class_name")
+ list_file = roll_call_list_dir / f"{class_name}.json"
+
+ # 如果文件不存在,返回空列表
+ if not list_file.exists():
+ self.initial_groups = []
+ return []
+
+ # 读取文件内容
+ with open_file(list_file, "r", encoding="utf-8") as f:
+ data = json.load(f)
+
+ # 获取所有小组(从每个学生的group字段)
+ groups = []
+ for student_name, student_info in data.items():
+ if "group" in student_info and student_info["group"]:
+ groups.append(student_info["group"])
+
+ self.initial_groups = groups.copy()
+
+ return groups
+
+ except Exception as e:
+ logger.error(f"加载小组失败: {str(e)}")
+ self.initial_groups = []
+ return []
+
+ def __create_button_area(self):
+ """创建按钮区域"""
+ # 创建按钮布局
+ button_layout = QHBoxLayout()
+
+ # 伸缩项
+ button_layout.addStretch(1)
+
+ # 保存按钮
+ self.save_button = PrimaryPushButton(
+ get_content_name_async("group_setting", "save_button")
+ )
+ self.save_button.setIcon(FluentIcon.SAVE)
+ button_layout.addWidget(self.save_button)
+
+ # 取消按钮
+ self.cancel_button = PushButton(
+ get_content_name_async("group_setting", "cancel_button")
+ )
+ self.cancel_button.setIcon(FluentIcon.CANCEL)
+ button_layout.addWidget(self.cancel_button)
+
+ # 添加到主布局
+ self.main_layout.addLayout(button_layout)
+
+ def __connect_signals(self):
+ """连接信号与槽"""
+ self.save_button.clicked.connect(self.__save_groups)
+ self.cancel_button.clicked.connect(self.__cancel)
+ # 添加文本变化监听器
+ self.text_edit.textChanged.connect(self.__on_text_changed)
+
+ def __on_text_changed(self):
+ """文本变化事件处理"""
+ # 获取当前文本中的小组
+ current_text = self.text_edit.toPlainText()
+ current_groups = [
+ group.strip() for group in current_text.split("\n") if group.strip()
+ ]
+
+ # 检查哪些初始小组被删除了
+ deleted_groups = [
+ group for group in self.initial_groups if group not in current_groups
+ ]
+
+ # 如果有小组被删除,显示提示
+ if deleted_groups:
+ for group in deleted_groups:
+ # 显示删除提示
+ config = NotificationConfig(
+ title=get_content_name_async(
+ "group_setting", "group_deleted_title"
+ ),
+ content=get_content_name_async(
+ "group_setting", "group_deleted_message"
+ ).format(group=group),
+ duration=3000,
+ )
+ show_notification(NotificationType.INFO, config, parent=self)
+
+ # 更新初始小组列表
+ self.initial_groups = current_groups.copy()
+
+ def __save_groups(self):
+ """保存小组"""
+ try:
+ # 获取输入的小组
+ groups_text = self.text_edit.toPlainText().strip()
+ if not groups_text:
+ # 显示错误消息
+ config = NotificationConfig(
+ title=get_content_name_async("group_setting", "error_title"),
+ content=get_content_name_async("group_setting", "no_groups_error"),
+ duration=3000,
+ )
+ show_notification(NotificationType.ERROR, config, parent=self)
+ return
+
+ # 分割小组
+ groups = [
+ group.strip() for group in groups_text.split("\n") if group.strip()
+ ]
+
+ # 验证小组
+ invalid_groups = []
+ for group in groups:
+ # 检查是否包含非法字符
+ if re.search(r'[\/:*?"<>|]', group):
+ invalid_groups.append(group)
+ # 检查是否为保留字
+ elif group.lower() == "class":
+ invalid_groups.append(group)
+
+ if invalid_groups:
+ # 显示错误消息
+ config = NotificationConfig(
+ title=get_content_name_async("group_setting", "error_title"),
+ content=get_content_name_async(
+ "group_setting", "invalid_groups_error"
+ ).format(groups=", ".join(invalid_groups)),
+ duration=5000,
+ )
+ show_notification(NotificationType.ERROR, config, parent=self)
+ return
+
+ # 获取文件路径
+ roll_call_list_dir = get_path("app/resources/list/roll_call_list")
+ roll_call_list_dir.mkdir(parents=True, exist_ok=True)
+
+ # 从设置中获取班级名称
+ class_name = readme_settings_async("roll_call_list", "select_class_name")
+ list_file = roll_call_list_dir / f"{class_name}.json"
+
+ # 读取现有数据
+ existing_data = {}
+ if list_file.exists():
+ with open_file(list_file, "r", encoding="utf-8") as f:
+ existing_data = json.load(f)
+
+ # 如果小组没有新增
+ existing_groups = []
+ for student_name, student_info in existing_data.items():
+ if "group" in student_info and student_info["group"]:
+ existing_groups.append(student_info["group"])
+
+ if set(groups) == set(existing_groups):
+ # 显示提示消息
+ config = NotificationConfig(
+ title=get_content_name_async("group_setting", "info_title"),
+ content=get_content_name_async(
+ "group_setting", "no_new_groups_message"
+ ),
+ duration=3000,
+ )
+ show_notification(NotificationType.INFO, config, parent=self)
+ return
+
+ # 更新现有数据中的小组信息
+ updated_data = existing_data.copy()
+
+ # 为每个学生更新小组信息
+ for student_name in updated_data:
+ # 如果学生没有小组字段,则添加空字段
+ if "group" not in updated_data[student_name]:
+ updated_data[student_name]["group"] = ""
+
+ # 保存到文件
+ with open_file(list_file, "w", encoding="utf-8") as f:
+ json.dump(updated_data, f, ensure_ascii=False, indent=4)
+
+ # 显示保存成功通知
+ config = NotificationConfig(
+ title=get_content_name_async("group_setting", "success_title"),
+ content=get_content_name_async(
+ "group_setting", "success_message"
+ ).format(count=len(groups)),
+ duration=3000,
+ )
+ show_notification(NotificationType.SUCCESS, config, parent=self)
+
+ # 更新初始小组列表
+ self.initial_groups = groups.copy()
+
+ # 标记为已保存
+ self.saved = True
+
+ except Exception as e:
+ # 显示错误消息
+ config = NotificationConfig(
+ title=get_content_name_async("group_setting", "error_title"),
+ content=f"{get_content_name_async('group_setting', 'save_error')}: {str(e)}",
+ duration=3000,
+ )
+ show_notification(NotificationType.ERROR, config, parent=self)
+ logger.error(f"保存小组失败: {e}")
+
+ def __cancel(self):
+ """取消操作"""
+ # 获取父窗口并关闭
+ parent = self.parent()
+ while parent:
+ # 查找SimpleWindowTemplate类型的父窗口
+ if hasattr(parent, "windowClosed") and hasattr(parent, "close"):
+ parent.close()
+ break
+ parent = parent.parent()
+
+ def closeEvent(self, event):
+ """窗口关闭事件处理"""
+ if not self.saved:
+ # 创建确认对话框
+ dialog = Dialog(
+ get_content_name_async("group_setting", "unsaved_changes_title"),
+ get_content_name_async("group_setting", "unsaved_changes_message"),
+ self,
+ )
+
+ dialog.yesButton.setText(
+ get_content_name_async("group_setting", "discard_button")
+ )
+ dialog.cancelButton.setText(
+ get_content_name_async("group_setting", "continue_editing_button")
+ )
+
+ # 显示对话框并获取用户选择
+ if dialog.exec():
+ # 用户选择放弃更改,关闭窗口
+ event.accept()
+ else:
+ # 用户选择继续编辑,取消关闭事件
+ event.ignore()
+ else:
+ # 已保存,直接关闭
+ event.accept()
diff --git a/app/view/another_window/import_student_name.py b/app/view/another_window/import_student_name.py
new file mode 100644
index 00000000..ed546944
--- /dev/null
+++ b/app/view/another_window/import_student_name.py
@@ -0,0 +1,721 @@
+# ==================================================
+# 导入库
+# ==================================================
+import os
+import json
+from typing import Dict, List, Any
+
+from loguru import logger
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from qfluentwidgets import *
+
+from app.tools.variable import *
+from app.tools.path_utils import *
+from app.tools.personalised import *
+from app.tools.settings_default import *
+from app.tools.settings_access import *
+from app.Language.obtain_language import *
+from app.tools.config import *
+
+
+class ImportStudentNameWindow(QWidget):
+ """学生姓名导入窗口"""
+
+ def __init__(self, parent=None):
+ """初始化学生姓名导入窗口"""
+ # 调用父类初始化方法
+ super().__init__(parent)
+
+ # 初始化变量
+ self.file_path = None
+ self.data = None
+ self.columns = []
+ self.column_mapping = {}
+ self.preview_data = []
+
+ # 创建UI
+ self.__init_ui()
+
+ # 连接信号
+ self.__connect_signals()
+
+ def __init_ui(self):
+ """初始化UI组件"""
+ # 创建主布局
+ self.main_layout = QVBoxLayout(self)
+ self.main_layout.setContentsMargins(20, 20, 20, 20)
+ self.main_layout.setSpacing(15)
+
+ # 创建标题
+ self.title_label = TitleLabel(
+ get_content_name_async("import_student_name", "title")
+ )
+ self.main_layout.addWidget(self.title_label)
+
+ # 创建当前导入班级的提示标签
+ self.class_name_label = SubtitleLabel(
+ get_content_name_async("import_student_name", "initial_subtitle")
+ + readme_settings_async("roll_call_list", "select_class_name")
+ )
+ self.main_layout.addWidget(self.class_name_label)
+
+ # 创建文件选择区域
+ self.__create_file_selection_area()
+
+ # 创建列映射区域
+ self.__create_column_mapping_area()
+
+ # 创建预览区域
+ self.__create_preview_area()
+
+ # 创建按钮区域
+ self.__create_button_area()
+
+ # 添加伸缩项
+ self.main_layout.addStretch(1)
+
+ # 初始状态下禁用部分控件
+ self.__update_ui_state()
+
+ def __create_file_selection_area(self):
+ """创建文件选择区域"""
+ # 创建卡片容器
+ file_card = CardWidget()
+ file_layout = QVBoxLayout(file_card)
+
+ # 创建文件选择区域标题
+ file_title = SubtitleLabel(
+ get_content_name_async("import_student_name", "file_selection_title")
+ )
+ file_layout.addWidget(file_title)
+
+ # 创建文件选择行
+ file_row = QHBoxLayout()
+
+ # 文件路径标签
+ self.file_path_label = BodyLabel(
+ get_content_name_async("import_student_name", "no_file_selected")
+ )
+ self.file_path_label.setWordWrap(True)
+ file_row.addWidget(self.file_path_label, 1)
+
+ # 选择文件按钮
+ self.select_file_btn = PrimaryPushButton(
+ get_content_name_async("import_student_name", "select_file")
+ )
+ self.select_file_btn.setIcon(FluentIcon.FOLDER)
+ self.select_file_btn.setFixedWidth(120)
+ file_row.addWidget(self.select_file_btn)
+
+ file_layout.addLayout(file_row)
+
+ # 支持格式提示
+ format_hint = CaptionLabel(
+ get_content_name_async("import_student_name", "supported_formats")
+ )
+ file_layout.addWidget(format_hint)
+
+ # 添加到主布局
+ self.main_layout.addWidget(file_card)
+
+ def __create_column_mapping_area(self):
+ """创建列映射区域"""
+ # 创建卡片容器
+ mapping_card = CardWidget()
+ mapping_layout = QVBoxLayout(mapping_card)
+
+ # 创建列映射区域标题
+ mapping_title = SubtitleLabel(
+ get_content_name_async("import_student_name", "column_mapping_title")
+ )
+ mapping_layout.addWidget(mapping_title)
+
+ # 创建列映射说明
+ mapping_desc = BodyLabel(
+ get_content_name_async("import_student_name", "column_mapping_description")
+ )
+ mapping_layout.addWidget(mapping_desc)
+
+ # 创建列映射表单布局
+ mapping_form = QVBoxLayout()
+
+ # 学号列选择(必选项,第一个)
+ id_row = QHBoxLayout()
+ id_label = BodyLabel(
+ get_content_name_async("import_student_name", "column_mapping_id_column")
+ )
+ self.id_column_combo = ComboBox()
+ self.id_column_combo.currentIndexChanged.connect(
+ self.__on_column_mapping_changed
+ )
+ id_row.addWidget(id_label)
+ id_row.addWidget(self.id_column_combo, 1)
+ mapping_form.addLayout(id_row)
+
+ # 姓名列选择(必选项,第二个)
+ name_row = QHBoxLayout()
+ name_label = BodyLabel(
+ get_content_name_async("import_student_name", "column_mapping_name_column")
+ )
+ self.name_column_combo = ComboBox()
+ self.name_column_combo.currentIndexChanged.connect(
+ self.__on_column_mapping_changed
+ )
+ name_row.addWidget(name_label)
+ name_row.addWidget(self.name_column_combo, 1)
+ mapping_form.addLayout(name_row)
+
+ # 性别列选择(可选,第三个)
+ gender_row = QHBoxLayout()
+ gender_label = BodyLabel(
+ get_content_name_async(
+ "import_student_name", "column_mapping_gender_column"
+ )
+ )
+ self.gender_column_combo = ComboBox()
+ self.gender_column_combo.currentIndexChanged.connect(
+ self.__on_column_mapping_changed
+ )
+ gender_row.addWidget(gender_label)
+ gender_row.addWidget(self.gender_column_combo, 1)
+ mapping_form.addLayout(gender_row)
+
+ # 小组列选择(可选,第四个)
+ group_row = QHBoxLayout()
+ group_label = BodyLabel(
+ get_content_name_async("import_student_name", "column_mapping_group_column")
+ )
+ self.group_column_combo = ComboBox()
+ self.group_column_combo.currentIndexChanged.connect(
+ self.__on_column_mapping_changed
+ )
+ group_row.addWidget(group_label)
+ group_row.addWidget(self.group_column_combo, 1)
+ mapping_form.addLayout(group_row)
+
+ mapping_layout.addLayout(mapping_form)
+
+ # 添加到主布局
+ self.main_layout.addWidget(mapping_card)
+
+ def __create_preview_area(self):
+ """创建预览区域"""
+ # 创建卡片容器
+ preview_card = CardWidget()
+ preview_layout = QVBoxLayout(preview_card)
+
+ # 创建预览区域标题
+ preview_title = SubtitleLabel(
+ get_content_name_async("import_student_name", "data_preview_title")
+ )
+ preview_layout.addWidget(preview_title)
+
+ # 创建预览表格
+ self.preview_table = TableWidget()
+ self.preview_table.setWordWrap(True)
+ self.preview_table.verticalHeader().setVisible(False)
+ preview_layout.addWidget(self.preview_table)
+
+ # 添加到主布局
+ self.main_layout.addWidget(preview_card)
+
+ def __create_button_area(self):
+ """创建按钮区域"""
+ # 创建按钮布局
+ button_layout = QHBoxLayout()
+
+ # 伸缩项
+ button_layout.addStretch(1)
+
+ # 导入按钮
+ self.import_btn = PrimaryPushButton(
+ get_content_name_async("import_student_name", "buttons_import")
+ )
+ self.import_btn.setIcon(FluentIcon.DOWNLOAD)
+ self.import_btn.setEnabled(False)
+ button_layout.addWidget(self.import_btn)
+
+ # 添加到主布局
+ self.main_layout.addLayout(button_layout)
+
+ def __connect_signals(self):
+ """连接信号"""
+ # 选择文件按钮
+ self.select_file_btn.clicked.connect(self.__select_file)
+
+ # 列映射变化
+ self.id_column_combo.currentIndexChanged.connect(
+ self.__on_column_mapping_changed
+ )
+ self.name_column_combo.currentIndexChanged.connect(
+ self.__on_column_mapping_changed
+ )
+ self.gender_column_combo.currentIndexChanged.connect(
+ self.__on_column_mapping_changed
+ )
+ self.group_column_combo.currentIndexChanged.connect(
+ self.__on_column_mapping_changed
+ )
+
+ # 按钮事件
+ self.import_btn.clicked.connect(self.__import_data)
+
+ def __update_ui_state(self):
+ """更新UI状态"""
+ has_file = self.file_path is not None
+ id_column = self.id_column_combo.currentText()
+ name_column = self.name_column_combo.currentText()
+
+ # 检查是否选择了"无"选项
+ if id_column == get_content_name_async(
+ "import_student_name", "column_mapping_none"
+ ):
+ id_column = ""
+ if name_column == get_content_name_async(
+ "import_student_name", "column_mapping_none"
+ ):
+ name_column = ""
+
+ has_id = bool(id_column)
+ has_name = bool(name_column)
+
+ logger.debug(
+ f"has_file: {has_file}, has_id: {id_column}, has_name: {name_column}"
+ )
+
+ # 更新控件状态
+ self.id_column_combo.setEnabled(has_file)
+ self.name_column_combo.setEnabled(has_file)
+ self.gender_column_combo.setEnabled(has_file)
+ self.group_column_combo.setEnabled(has_file)
+
+ # 导入按钮需要同时有文件、学号映射和姓名映射
+ if hasattr(self, "import_btn"):
+ self.import_btn.setEnabled(has_file and has_id and has_name)
+
+ def __on_column_mapping_changed(self):
+ """列映射变化时的处理"""
+ # 检查UI组件是否已初始化
+ if not hasattr(self, "preview_table"):
+ return
+
+ # 更新预览
+ self.__update_preview()
+ # 更新UI状态
+ self.__update_ui_state()
+
+ def __select_file(self):
+ """选择文件"""
+ # 定义支持的文件过滤器
+ file_filter = get_content_name_async("import_student_name", "file_filter")
+
+ # 打开文件对话框
+ file_path, _ = QFileDialog.getOpenFileName(
+ self,
+ get_content_name_async("import_student_name", "dialog_title"),
+ "",
+ file_filter,
+ )
+
+ if file_path:
+ try:
+ # 加载文件
+ self.__load_file(file_path)
+
+ # 更新文件路径标签
+ self.file_path_label.setText(os.path.basename(file_path))
+
+ # 显示成功消息
+ config = NotificationConfig(
+ title=get_content_name_async(
+ "import_student_name", "file_loaded_notification_title"
+ ),
+ content=get_content_name_async(
+ "import_student_name", "file_loaded_notification_content"
+ ),
+ duration=3000,
+ )
+ show_notification(NotificationType.SUCCESS, config, parent=self)
+
+ except Exception as e:
+ # 显示错误消息
+ config = NotificationConfig(
+ title=get_content_name_async(
+ "import_student_name", "load_failed_notification_title"
+ ),
+ content=get_content_name_async(
+ "import_student_name", "load_failed_notification_content"
+ ),
+ duration=3000,
+ )
+ show_notification(NotificationType.ERROR, config, parent=self)
+ logger.error(f"加载文件失败: {e}")
+
+ def __load_file(self, file_path: str):
+ """加载文件"""
+ self.file_path = file_path
+
+ # 根据文件扩展名选择加载方法
+ file_ext = os.path.splitext(file_path)[1].lower()
+
+ if file_ext in [".xlsx", ".xls"]:
+ # 延迟导入 pandas,避免在模块导入时加载大型 C 扩展
+ try:
+ import pandas as pd
+ except Exception as e:
+ logger.error(f"加载 Excel 需要 pandas 库,但导入失败: {e}")
+ raise
+
+ # 加载Excel文件
+ self.data = pd.read_excel(file_path)
+ elif file_ext == ".csv":
+ # 延迟导入 pandas,避免在模块导入时加载大型 C 扩展
+ try:
+ import pandas as pd
+ except Exception as e:
+ logger.error(f"加载 CSV 需要 pandas 库,但导入失败: {e}")
+ raise
+
+ # 加载CSV文件
+ self.data = pd.read_csv(file_path)
+ else:
+ raise ValueError(
+ get_content_name_async("import_student_name", "unsupported_format")
+ )
+
+ # 获取列名
+ self.columns = list(self.data.columns)
+
+ # 更新列映射下拉框
+ self.__update_column_combos()
+
+ # 尝试自动映射列
+ self.__auto_map_columns()
+
+ # 更新预览
+ self.__update_preview()
+
+ # 更新UI状态
+ self.__update_ui_state()
+
+ def __update_column_combos(self):
+ """更新列映射下拉框"""
+ # 清空现有选项
+ self.name_column_combo.clear()
+ self.id_column_combo.clear()
+ self.gender_column_combo.clear()
+ self.group_column_combo.clear()
+
+ # 为所有列添加"无"选项
+ none_text = get_content_name_async("import_student_name", "column_mapping_none")
+ self.gender_column_combo.addItem(none_text)
+ self.group_column_combo.addItem(none_text)
+
+ # 添加所有列
+ for column in self.columns:
+ self.name_column_combo.addItem(column)
+ self.id_column_combo.addItem(column)
+ self.gender_column_combo.addItem(column)
+ self.group_column_combo.addItem(column)
+
+ def __auto_map_columns(self):
+ """自动映射列"""
+ # 学号列可能的关键词(优先级从高到低)
+ id_keywords = ["学号", "学生ID", "student id", "编号", "id", "no"]
+
+ # 姓名列可能的关键词(优先级从高到低)
+ name_keywords = ["姓名", "学生姓名", "名字", "student name", "名", "name"]
+
+ # 性别列可能的关键词(优先级从高到低)
+ gender_keywords = ["性别", "男女", "gender", "sex"]
+
+ # 小组列可能的关键词(优先级从高到低)
+ group_keywords = ["小组", "分组", "组别", "队伍", "group", "team"]
+
+ # 使用更精确的匹配方法
+ def find_best_match(keywords, columns):
+ """找到最佳匹配的列"""
+ best_match = None
+ best_score = 0
+
+ for column in columns:
+ column_lower = column.lower()
+ for i, keyword in enumerate(keywords):
+ # 完全匹配得分最高
+ if column_lower == keyword.lower():
+ score = 100 - i # 根据关键词优先级计算得分
+ if score > best_score:
+ best_score = score
+ best_match = column
+ # 包含关键词得分次之
+ elif keyword.lower() in column_lower:
+ score = 50 - i # 根据关键词优先级计算得分
+ if score > best_score:
+ best_score = score
+ best_match = column
+
+ return best_match
+
+ # 自动映射学号列
+ id_match = find_best_match(id_keywords, self.columns)
+ if id_match:
+ index = self.id_column_combo.findText(id_match)
+ if index >= 0:
+ self.id_column_combo.setCurrentIndex(index)
+
+ # 自动映射姓名列
+ name_match = find_best_match(name_keywords, self.columns)
+ if name_match:
+ index = self.name_column_combo.findText(name_match)
+ if index >= 0:
+ self.name_column_combo.setCurrentIndex(index)
+
+ # 自动映射性别列
+ gender_match = find_best_match(gender_keywords, self.columns)
+ if gender_match:
+ index = self.gender_column_combo.findText(gender_match)
+ if index >= 0:
+ self.gender_column_combo.setCurrentIndex(index)
+
+ # 自动映射小组列
+ group_match = find_best_match(group_keywords, self.columns)
+ if group_match:
+ index = self.group_column_combo.findText(group_match)
+ if index >= 0:
+ self.group_column_combo.setCurrentIndex(index)
+
+ def __update_preview(self):
+ """更新预览"""
+ if self.data is None:
+ return
+
+ id_column = self.id_column_combo.currentText()
+ name_column = self.name_column_combo.currentText()
+ gender_column = self.gender_column_combo.currentText()
+ group_column = self.group_column_combo.currentText()
+
+ # 检查是否选择了"无"选项(空字符串)
+ if not id_column and not name_column:
+ return
+ # 如果选择了"无"选项,将其设为None
+ if id_column == get_content_name_async(
+ "import_student_name", "column_mapping_none"
+ ):
+ id_column = None
+ if name_column == get_content_name_async(
+ "import_student_name", "column_mapping_none"
+ ):
+ name_column = None
+ if gender_column == get_content_name_async(
+ "import_student_name", "column_mapping_none"
+ ):
+ gender_column = None
+ if group_column == get_content_name_async(
+ "import_student_name", "column_mapping_none"
+ ):
+ group_column = None
+
+ if not id_column and not name_column:
+ return
+
+ # 创建预览数据
+ preview_columns = []
+ preview_headers = []
+
+ # 按照顺序添加列:学号、姓名、性别、小组
+ if id_column:
+ preview_columns.append(id_column)
+ preview_headers.append(
+ get_content_name_async("import_student_name", "student_id")
+ )
+
+ if name_column:
+ preview_columns.append(name_column)
+ preview_headers.append(
+ get_content_name_async("import_student_name", "name")
+ )
+
+ if gender_column:
+ preview_columns.append(gender_column)
+ preview_headers.append(
+ get_content_name_async("import_student_name", "gender")
+ )
+
+ if group_column:
+ preview_columns.append(group_column)
+ preview_headers.append(
+ get_content_name_async("import_student_name", "group")
+ )
+
+ # 限制预览行数
+ max_rows = min(10, len(self.data))
+ preview_df = self.data[preview_columns].head(max_rows).reset_index(drop=True)
+
+ # 更新表格
+ self.preview_table.setRowCount(max_rows)
+ self.preview_table.setColumnCount(len(preview_columns))
+ self.preview_table.setHorizontalHeaderLabels(preview_headers)
+
+ # 填充数据
+ for i in range(max_rows):
+ for j, column in enumerate(preview_columns):
+ item = QTableWidgetItem(str(preview_df.iloc[i, j]))
+ self.preview_table.setItem(i, j, item)
+
+ # 调整列宽
+ self.preview_table.resizeColumnsToContents()
+
+ def __import_data(self):
+ """导入数据"""
+ try:
+ id_column = self.id_column_combo.currentText()
+ name_column = self.name_column_combo.currentText()
+ gender_column = self.gender_column_combo.currentText()
+ group_column = self.group_column_combo.currentText()
+
+ # 验证必选项:学号和姓名列都必须选择
+ if not id_column:
+ raise ValueError(
+ get_content_name_async("import_student_name", "no_id_column")
+ )
+
+ if not name_column:
+ raise ValueError(
+ get_content_name_async("import_student_name", "no_name_column")
+ )
+
+ # 检查是否选择了"无"选项(空字符串)
+ if gender_column == get_content_name_async(
+ "import_student_name", "column_mapping_none"
+ ):
+ gender_column = None
+ if group_column == get_content_name_async(
+ "import_student_name", "column_mapping_none"
+ ):
+ group_column = None
+
+ # 提取数据
+ student_data = []
+ for _, row in self.data.iterrows():
+ student_info = {
+ "id": str(row[id_column]).strip(),
+ "name": str(row[name_column]).strip(),
+ "gender": str(row[gender_column]).strip() if gender_column else "",
+ "group": str(row[group_column]).strip() if group_column else "",
+ "exist": True,
+ }
+
+ # 验证姓名不为空
+ if student_info["name"]:
+ student_data.append(student_info)
+
+ # 获取班级名称并进行有效性检查
+ class_name = readme_settings_async("roll_call_list", "select_class_name")
+ self.__save_student_data(class_name, student_data)
+
+ # 显示成功消息
+ config = NotificationConfig(
+ title=get_content_name_async(
+ "import_student_name", "import_success_notification_title"
+ ),
+ content=get_content_name_async(
+ "import_student_name",
+ "import_success_notification_content_template",
+ ).format(count=len(student_data), class_name=class_name),
+ duration=3000,
+ )
+ show_notification(NotificationType.SUCCESS, config, parent=self)
+
+ except Exception as e:
+ # 显示错误消息
+ config = NotificationConfig(
+ title=get_content_name_async(
+ "import_student_name", "import_failed_notification_title"
+ ),
+ content=f"{get_content_name_async('import_student_name', 'import_failed_notification_content')}: {str(e)}",
+ duration=3000,
+ )
+ show_notification(NotificationType.ERROR, config, parent=self)
+ logger.error(f"导入数据失败: {e}")
+
+ def __save_student_data(self, class_name: str, student_data: List[Dict[str, Any]]):
+ """保存学生数据到班级名单文件"""
+ # 确保班级名单目录存在
+ roll_call_list_dir = get_path("app/resources/list/roll_call_list")
+ roll_call_list_dir.mkdir(parents=True, exist_ok=True)
+
+ # 创建班级名单文件路径
+ class_file = roll_call_list_dir / f"{class_name}.json"
+
+ # 如果文件已存在,读取现有数据
+ existing_data = {}
+ if class_file.exists():
+ with open_file(class_file, "r", encoding="utf-8") as f:
+ existing_data = json.load(f)
+
+ # 如果有现有数据,让用户选择处理方式
+ if existing_data:
+ # 创建选择对话框
+ dialog = MessageBox(
+ get_content_name_async("import_student_name", "existing_data_title"),
+ get_content_name_async(
+ "import_student_name", "existing_data_prompt"
+ ).format(class_name=class_name, count=len(existing_data)),
+ self,
+ )
+
+ dialog.yesButton.setText(
+ get_content_name_async(
+ "import_student_name", "existing_data_option_overwrite"
+ )
+ )
+ dialog.cancelButton.setText(
+ get_content_name_async(
+ "import_student_name", "existing_data_option_cancel"
+ )
+ )
+
+ # 显示对话框并获取用户选择
+ if dialog.exec():
+ all_students = {}
+ for student in student_data:
+ all_students[student["name"]] = {
+ "id": int(student["id"])
+ if str(student["id"]).isdigit()
+ else student["id"],
+ "gender": student["gender"] if student["gender"] else "",
+ "group": student["group"] if student["group"] else "",
+ "exist": student.get("exist", True),
+ }
+ action = "overwrite"
+ else:
+ # 用户取消导入
+ return
+ else:
+ # 没有现有数据,直接保存
+ # 将列表结构转换为字典结构,符合学生名单文件格式
+ all_students = {}
+ for student in student_data:
+ # 确保数据格式与现有学生名单文件一致
+ all_students[student["name"]] = {
+ "id": int(student["id"])
+ if str(student["id"]).isdigit()
+ else student["id"],
+ "gender": student["gender"] if student["gender"] else "",
+ "group": student["group"] if student["group"] else "",
+ "exist": student.get("exist", True),
+ }
+ action = "new"
+
+ # 保存到文件
+ with open_file(class_file, "w", encoding="utf-8") as f:
+ json.dump(all_students, f, ensure_ascii=False, indent=4)
+
+ if action == "overwrite":
+ logger.info(
+ f"已覆盖班级 '{class_name}' 的数据,共 {len(all_students)} 名学生"
+ )
+ else:
+ logger.info(f"已保存 {len(all_students)} 名学生到新班级 '{class_name}'")
diff --git a/app/view/another_window/name_setting.py b/app/view/another_window/name_setting.py
new file mode 100644
index 00000000..93bc3bcd
--- /dev/null
+++ b/app/view/another_window/name_setting.py
@@ -0,0 +1,333 @@
+# ==================================================
+# 导入库
+# ==================================================
+import re
+import json
+
+from loguru import logger
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from qfluentwidgets import *
+
+from app.tools.variable import *
+from app.tools.path_utils import *
+from app.tools.personalised import *
+from app.tools.settings_default import *
+from app.tools.settings_access import *
+from app.Language.obtain_language import *
+from app.tools.config import *
+from app.tools.list import *
+
+
+class NameSettingWindow(QWidget):
+ """姓名设置窗口"""
+
+ def __init__(self, parent=None):
+ """初始化姓名设置窗口"""
+ super().__init__(parent)
+
+ # 初始化变量
+ self.saved = False
+ self.initial_names = [] # 保存初始加载的姓名列表
+
+ # 初始化UI
+ self.init_ui()
+
+ # 连接信号
+ self.__connect_signals()
+
+ def init_ui(self):
+ """初始化UI"""
+ # 设置窗口标题
+ self.setWindowTitle(get_content_name_async("name_setting", "title"))
+
+ # 创建主布局
+ self.main_layout = QVBoxLayout(self)
+ self.main_layout.setContentsMargins(20, 20, 20, 20)
+ self.main_layout.setSpacing(15)
+
+ # 创建标题
+ self.title_label = TitleLabel(get_content_name_async("name_setting", "title"))
+ self.main_layout.addWidget(self.title_label)
+
+ # 创建说明标签
+ self.description_label = BodyLabel(
+ get_content_name_async("name_setting", "description")
+ )
+ self.description_label.setWordWrap(True)
+ self.main_layout.addWidget(self.description_label)
+
+ # 创建姓名输入区域
+ self.__create_name_input_area()
+
+ # 创建按钮区域
+ self.__create_button_area()
+
+ # 添加伸缩项
+ self.main_layout.addStretch(1)
+
+ def __create_name_input_area(self):
+ """创建姓名输入区域"""
+ # 创建卡片容器
+ input_card = CardWidget()
+ input_layout = QVBoxLayout(input_card)
+
+ # 创建输入区域标题
+ input_title = SubtitleLabel(
+ get_content_name_async("name_setting", "input_title")
+ )
+ input_layout.addWidget(input_title)
+
+ # 创建文本编辑框
+ self.text_edit = PlainTextEdit()
+ self.text_edit.setPlaceholderText(
+ get_content_name_async("name_setting", "input_placeholder")
+ )
+
+ # 加载现有姓名
+ existing_names = self.__load_existing_names()
+ if existing_names:
+ self.text_edit.setPlainText("\n".join(existing_names))
+
+ input_layout.addWidget(self.text_edit)
+
+ # 添加到主布局
+ self.main_layout.addWidget(input_card)
+
+ def __load_existing_names(self):
+ """加载现有姓名"""
+ try:
+ # 获取班级名单目录
+ roll_call_list_dir = get_path("app/resources/list/roll_call_list")
+
+ # 从设置中获取班级名称
+ class_name = readme_settings_async("roll_call_list", "select_class_name")
+ list_file = roll_call_list_dir / f"{class_name}.json"
+
+ # 如果文件不存在,返回空列表
+ if not list_file.exists():
+ self.initial_names = []
+ return []
+
+ # 读取文件内容
+ with open_file(list_file, "r", encoding="utf-8") as f:
+ data = json.load(f)
+
+ # 获取所有姓名(字典的键)
+ names = list(data.keys())
+ # 保存到initial_names变量
+ self.initial_names = names.copy()
+
+ return names
+
+ except Exception as e:
+ logger.error(f"加载姓名失败: {str(e)}")
+ self.initial_names = []
+ return []
+
+ def __create_button_area(self):
+ """创建按钮区域"""
+ # 创建按钮布局
+ button_layout = QHBoxLayout()
+
+ # 伸缩项
+ button_layout.addStretch(1)
+
+ # 保存按钮
+ self.save_button = PrimaryPushButton(
+ get_content_name_async("name_setting", "save_button")
+ )
+ self.save_button.setIcon(FluentIcon.SAVE)
+ button_layout.addWidget(self.save_button)
+
+ # 取消按钮
+ self.cancel_button = PushButton(
+ get_content_name_async("name_setting", "cancel_button")
+ )
+ self.cancel_button.setIcon(FluentIcon.CANCEL)
+ button_layout.addWidget(self.cancel_button)
+
+ # 添加到主布局
+ self.main_layout.addLayout(button_layout)
+
+ def __connect_signals(self):
+ """连接信号与槽"""
+ self.save_button.clicked.connect(self.__save_names)
+ self.cancel_button.clicked.connect(self.__cancel)
+ # 添加文本变化监听器
+ self.text_edit.textChanged.connect(self.__on_text_changed)
+
+ def __on_text_changed(self):
+ """文本变化事件处理"""
+ # 获取当前文本中的姓名
+ current_text = self.text_edit.toPlainText()
+ current_names = [
+ name.strip() for name in current_text.split("\n") if name.strip()
+ ]
+
+ # 检查哪些初始姓名被删除了
+ deleted_names = [
+ name for name in self.initial_names if name not in current_names
+ ]
+
+ # 如果有姓名被删除,显示提示
+ if deleted_names:
+ for name in deleted_names:
+ # 显示删除提示
+ config = NotificationConfig(
+ title=get_content_name_async("name_setting", "name_deleted_title"),
+ content=get_content_name_async(
+ "name_setting", "name_deleted_message"
+ ).format(name=name),
+ duration=3000,
+ )
+ show_notification(NotificationType.INFO, config, parent=self)
+
+ # 更新初始姓名列表为当前列表,避免重复提示
+ self.initial_names = current_names.copy()
+
+ def __save_names(self):
+ """保存姓名"""
+ try:
+ # 获取输入的姓名
+ names_text = self.text_edit.toPlainText().strip()
+ if not names_text:
+ # 显示错误消息
+ config = NotificationConfig(
+ title=get_content_name_async("name_setting", "error_title"),
+ content=get_content_name_async("name_setting", "no_names_error"),
+ duration=3000,
+ )
+ show_notification(NotificationType.ERROR, config, parent=self)
+ return
+
+ # 分割姓名
+ names = [name.strip() for name in names_text.split("\n") if name.strip()]
+
+ # 验证姓名
+ invalid_names = []
+ for name in names:
+ # 检查是否包含非法字符
+ if re.search(r'[\/:*?"<>|]', name):
+ invalid_names.append(name)
+ # 检查是否为保留字
+ elif name.lower() == "class":
+ invalid_names.append(name)
+
+ if invalid_names:
+ # 显示错误消息
+ config = NotificationConfig(
+ title=get_content_name_async("name_setting", "error_title"),
+ content=get_content_name_async(
+ "name_setting", "invalid_names_error"
+ ).format(names=", ".join(invalid_names)),
+ duration=5000,
+ )
+ show_notification(NotificationType.ERROR, config, parent=self)
+ return
+
+ # 获取文件路径
+ roll_call_list_dir = get_path("app/resources/list/roll_call_list")
+ roll_call_list_dir.mkdir(parents=True, exist_ok=True)
+
+ # 从设置中获取班级名称
+ class_name = readme_settings_async("roll_call_list", "select_class_name")
+ list_file = roll_call_list_dir / f"{class_name}.json"
+
+ # 读取现有数据
+ existing_data = {}
+ if list_file.exists():
+ with open_file(list_file, "r", encoding="utf-8") as f:
+ existing_data = json.load(f)
+
+ # 如果名称没有新增
+ if set(names) == set(existing_data.keys()):
+ # 显示提示消息
+ config = NotificationConfig(
+ title=get_content_name_async("name_setting", "info_title"),
+ content=get_content_name_async(
+ "name_setting", "no_new_names_message"
+ ),
+ duration=3000,
+ )
+ show_notification(NotificationType.INFO, config, parent=self)
+ return
+
+ # 创建新的学生数据字典
+ new_data = {}
+
+ # 为新姓名分配学号(从1开始递增)
+ for i, name in enumerate(names, 1):
+ # 如果姓名已存在于现有数据中,保留原有信息
+ if name in existing_data:
+ new_data[name] = existing_data[name]
+ else:
+ # 新增的姓名,分配新的学号和默认值
+ new_data[name] = {"id": i, "gender": "", "group": "", "exist": True}
+
+ # 保存到文件
+ with open_file(list_file, "w", encoding="utf-8") as f:
+ json.dump(new_data, f, ensure_ascii=False, indent=4)
+
+ # 显示保存成功通知
+ config = NotificationConfig(
+ title=get_content_name_async("name_setting", "success_title"),
+ content=get_content_name_async(
+ "name_setting", "success_message"
+ ).format(count=len(names)),
+ duration=3000,
+ )
+ show_notification(NotificationType.SUCCESS, config, parent=self)
+
+ # 标记为已保存
+ self.saved = True
+
+ except Exception as e:
+ # 显示错误消息
+ config = NotificationConfig(
+ title=get_content_name_async("name_setting", "error_title"),
+ content=f"{get_content_name_async('name_setting', 'save_error')}: {str(e)}",
+ duration=3000,
+ )
+ show_notification(NotificationType.ERROR, config, parent=self)
+ logger.error(f"保存姓名失败: {e}")
+
+ def __cancel(self):
+ """取消操作"""
+ # 获取父窗口并关闭
+ parent = self.parent()
+ while parent:
+ # 查找SimpleWindowTemplate类型的父窗口
+ if hasattr(parent, "windowClosed") and hasattr(parent, "close"):
+ parent.close()
+ break
+ parent = parent.parent()
+
+ def closeEvent(self, event):
+ """窗口关闭事件处理"""
+ if not self.saved:
+ # 创建确认对话框
+ dialog = Dialog(
+ get_content_name_async("name_setting", "unsaved_changes_title"),
+ get_content_name_async("name_setting", "unsaved_changes_message"),
+ self,
+ )
+
+ dialog.yesButton.setText(
+ get_content_name_async("name_setting", "discard_button")
+ )
+ dialog.cancelButton.setText(
+ get_content_name_async("name_setting", "continue_editing_button")
+ )
+
+ # 显示对话框并获取用户选择
+ if dialog.exec():
+ # 用户选择放弃更改,关闭窗口
+ event.accept()
+ else:
+ # 用户选择继续编辑,取消关闭事件
+ event.ignore()
+ else:
+ # 已保存,直接关闭
+ event.accept()
diff --git a/app/view/another_window/remaining_list.py b/app/view/another_window/remaining_list.py
new file mode 100644
index 00000000..a558d9ef
--- /dev/null
+++ b/app/view/another_window/remaining_list.py
@@ -0,0 +1,578 @@
+"""
+剩余名单页面
+用于显示未抽取的学生名单
+"""
+
+import json
+from typing import Dict, Any
+
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from qfluentwidgets import *
+
+from app.tools.variable import *
+from app.tools.path_utils import *
+from app.tools.personalised import *
+from app.tools.settings_default import *
+from app.tools.settings_access import *
+from app.Language.obtain_language import *
+from app.tools.config import *
+from app.tools.list import *
+
+
+class RemainingListPage(QWidget):
+ """剩余名单页面类"""
+
+ # 定义信号,当剩余人数变化时发出
+ count_changed = Signal(int)
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.class_name = ""
+ self.group_filter = ""
+ self.gender_filter = ""
+ self.half_repeat = 0
+ self.group_index = 0
+ self.gender_index = 0
+ self.remaining_students = []
+
+ # 布局更新状态跟踪
+ self._last_layout_width = 0
+ self._last_card_count = 0
+ self._layout_update_in_progress = False
+ self._resize_timer = None
+
+ self.init_ui()
+
+ # 延迟加载学生数据
+ QTimer.singleShot(100, self.load_student_data)
+
+ def init_ui(self):
+ """初始化UI"""
+ # 主布局
+ self.main_layout = QVBoxLayout(self)
+ self.main_layout.setContentsMargins(10, 10, 10, 10)
+ self.main_layout.setSpacing(10)
+
+ # 使用异步函数获取标题文本
+ title_text = get_content_name_async("remaining_list", "title")
+ count_text = get_any_position_value_async(
+ "remaining_list", "count_label", "name"
+ )
+
+ # 标题
+ self.title_label = SubtitleLabel(title_text)
+ self.title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
+ self.title_label.setFont(QFont(load_custom_font(), 18))
+ self.main_layout.addWidget(self.title_label)
+
+ # 剩余人数标签
+ self.count_label = BodyLabel(count_text.format(count=0))
+ self.count_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
+ self.count_label.setFont(QFont(load_custom_font(), 12))
+ self.main_layout.addWidget(self.count_label)
+
+ # 创建网格布局
+ self.grid_layout = QGridLayout()
+ self.grid_layout.setSpacing(STUDENT_CARD_SPACING)
+ self.main_layout.addLayout(self.grid_layout)
+
+ # 初始化卡片列表
+ self.cards = []
+
+ def get_students_file(self):
+ """获取学生数据文件路径"""
+ # 获取班级名单文件路径
+ roll_call_list_dir = get_path("app/resources/list/roll_call_list")
+ students_file = roll_call_list_dir / f"{self.class_name}.json"
+ return students_file
+
+ def load_student_data(self):
+ """加载学生数据"""
+ self._load_and_update_students()
+
+ def update_ui(self):
+ """更新UI显示"""
+ # 使用异步函数获取文本
+ title_text = get_any_position_value_async(
+ "remaining_list", "title_with_class", "name"
+ )
+ count_text = get_any_position_value_async(
+ "remaining_list", "count_label", "name"
+ )
+ group_count_text = get_any_position_value_async(
+ "remaining_list", "group_count_label", "name"
+ )
+
+ # 更新标题和人数/组数
+ self.title_label.setText(title_text.format(class_name=self.class_name))
+
+ # 检查是否显示小组
+ is_showing_groups = any(student.get("is_group", False) for student in self.students) if self.students else False
+
+ if is_showing_groups:
+ # 显示组数
+ group_count = len(self.students)
+ self.count_label.setText(group_count_text.format(count=group_count))
+ else:
+ # 显示人数
+ self.count_label.setText(count_text.format(count=len(self.students)))
+
+ # 清空现有卡片
+ self.cards = []
+ self._clear_grid_layout()
+
+ # 创建学生卡片
+ for student in self.students:
+ card = self.create_student_card(student)
+ if card is not None:
+ self.cards.append(card)
+
+ # 直接更新布局,不使用延迟
+ self.update_layout()
+
+ def update_layout(self):
+ """更新布局"""
+ if not self.grid_layout or not self.cards:
+ return
+
+ # 检查是否需要更新布局
+ current_width = self.width()
+ current_card_count = len(self.cards)
+
+ # 如果布局正在更新中,或者宽度和卡片数量都没有变化,则跳过更新
+ if (self._layout_update_in_progress or
+ (current_width == self._last_layout_width and
+ current_card_count == self._last_card_count)):
+ logger.debug(f"跳过布局更新: 宽度={current_width}, 卡片数={current_card_count}")
+ return
+
+ # 设置布局更新标志
+ self._layout_update_in_progress = True
+ self._last_layout_width = current_width
+ self._last_card_count = current_card_count
+
+ try:
+ # 清空现有布局
+ self._clear_grid_layout()
+
+ # 计算列数
+ def calculate_columns(width):
+ """根据窗口宽度和卡片尺寸动态计算列数"""
+ if width <= 0:
+ return 1
+
+ # 计算可用宽度(减去左右边距)
+ available_width = width - 40 # 左右各20px边距
+
+ # 所有卡片使用相同的尺寸
+ card_actual_width = STUDENT_CARD_MIN_WIDTH + STUDENT_CARD_SPACING
+ max_cols = STUDENT_MAX_COLUMNS
+
+ # 计算最大可能列数(不超过最大列数限制)
+ cols = min(int(available_width // card_actual_width), max_cols)
+
+ # 至少显示1列
+ return max(cols, 1)
+
+ # 获取当前窗口宽度
+ window_width = max(self.width(), self.sizeHint().width())
+ columns = calculate_columns(window_width)
+
+ # 添加卡片到网格布局
+ for i, card in enumerate(self.cards):
+ row = i // columns
+ col = i % columns
+ self.grid_layout.addWidget(card, row, col)
+ # 确保卡片可见
+ card.show()
+
+ # 设置列的伸缩因子,使卡片均匀分布
+ for col in range(columns):
+ self.grid_layout.setColumnStretch(col, 1)
+
+ logger.debug(f"布局更新完成: 宽度={window_width}, 列数={columns}, 卡片数={len(self.cards)}")
+ finally:
+ # 清除布局更新标志
+ self._layout_update_in_progress = False
+
+ def _clear_grid_layout(self):
+ """清空网格布局"""
+ # 重置列伸缩因子
+ for col in range(self.grid_layout.columnCount()):
+ self.grid_layout.setColumnStretch(col, 0)
+
+ while self.grid_layout.count():
+ item = self.grid_layout.takeAt(0)
+ widget = item.widget()
+ if widget:
+ widget.hide()
+ widget.setParent(None)
+
+ def create_student_card(self, student: Dict[str, Any]) -> CardWidget:
+ """创建学生卡片
+
+ Args:
+ student: 学生信息字典
+
+ Returns:
+ 学生卡片
+ """
+ # 检查是否是小组卡片
+ is_group = student.get("is_group", False)
+
+ card = CardWidget()
+
+ # 设置卡片属性,标记是否是小组卡片
+ card.setProperty("is_group", is_group)
+
+ if is_group:
+ # 小组卡片使用与学生卡片相同的宽度,但高度自适应
+ card.setMinimumSize(STUDENT_CARD_FIXED_WIDTH, 0)
+ card.setMaximumSize(STUDENT_CARD_FIXED_WIDTH, 500)
+ layout = QVBoxLayout(card)
+ layout.setContentsMargins(STUDENT_CARD_MARGIN, STUDENT_CARD_MARGIN,
+ STUDENT_CARD_MARGIN, STUDENT_CARD_MARGIN)
+ layout.setSpacing(8)
+
+ # 小组名称
+ name_label = BodyLabel(student["name"])
+ name_label.setFont(QFont(load_custom_font(), 16, QFont.Weight.Bold))
+ name_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
+ name_label.setWordWrap(True) # 启用自动换行
+ layout.addWidget(name_label)
+
+ # 小组成员数量
+ members = student.get("members", [])
+ count_label = BodyLabel(f"成员数量: {len(members)}")
+ count_label.setFont(QFont(load_custom_font(), 10))
+ count_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
+ layout.addWidget(count_label)
+
+ # 小组成员列表
+ members_names = [member['name'] for member in members[:5]] # 最多显示5个成员
+ members_text = "、".join(members_names)
+ if len(members) > 5:
+ members_text += f" 等{len(members)-5}名成员"
+
+ members_label = BodyLabel(members_text)
+ members_label.setFont(QFont(load_custom_font(), 9))
+ members_label.setAlignment(Qt.AlignmentFlag.AlignLeft)
+ members_label.setWordWrap(True) # 启用自动换行
+ layout.addWidget(members_label)
+ else:
+ # 普通学生卡片
+ card.setFixedSize(STUDENT_CARD_FIXED_WIDTH, STUDENT_CARD_FIXED_HEIGHT)
+
+ layout = QVBoxLayout(card)
+ layout.setContentsMargins(STUDENT_CARD_MARGIN, STUDENT_CARD_MARGIN,
+ STUDENT_CARD_MARGIN, STUDENT_CARD_MARGIN)
+ layout.setSpacing(5)
+
+ # 使用异步函数获取学生信息格式文本
+ student_info_text = get_any_position_value_async(
+ "remaining_list", "student_info", "name"
+ )
+
+ # 学生姓名
+ name_label = BodyLabel(student["name"])
+ name_label.setFont(QFont(load_custom_font(), 14))
+ name_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
+ layout.addWidget(name_label)
+
+ # 学生信息
+ info_text = student_info_text.format(
+ id=student["id"], gender=student["gender"], group=student["group"]
+ )
+ info_label = BodyLabel(info_text)
+ info_label.setFont(QFont(load_custom_font(), 9))
+ info_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
+ layout.addWidget(info_label)
+
+ return card
+
+ def _load_and_update_students(self, count=None):
+ """加载学生数据并更新UI的通用方法
+
+ Args:
+ count: 剩余人数,用于特殊处理当count为0时显示所有学生
+ """
+ # 设置更新标志,防止递归
+ self._updating = True
+
+ try:
+ # 获取学生数据
+ students_file = self.get_students_file()
+ with open(students_file, "r", encoding="utf-8") as f:
+ data = json.load(f)
+
+ # 获取索引
+ group_index = getattr(self, "group_index", 0)
+ gender_index = getattr(self, "gender_index", 0)
+
+ # 转换为字典格式
+ students_dict_list = []
+ for name, student_data in data.items():
+ student_dict = {
+ "id": student_data.get("id", ""),
+ "name": name,
+ "gender": student_data.get("gender", ""),
+ "group": student_data.get("group", ""),
+ "exist": student_data.get("exist", True),
+ }
+ students_dict_list.append(student_dict)
+
+ # 根据小组和性别筛选
+ filtered_students = students_dict_list
+
+ # 小组筛选
+ if group_index > 0:
+ # 获取所有可用小组
+ groups = set()
+ for student in students_dict_list:
+ if "group" in student and student["group"]:
+ groups.add(student["group"])
+
+ # 排序小组列表
+ sorted_groups = sorted(list(groups))
+
+ # 处理"抽取全部小组"的情况 (group_index == 1)
+ if group_index == 1:
+ # 创建小组数据结构,每个小组包含组名和成员列表
+ group_data = {}
+ for student in students_dict_list:
+ group_name = student.get("group", "")
+ if group_name: # 只处理有组名的小组
+ if group_name not in group_data:
+ group_data[group_name] = []
+ group_data[group_name].append(student)
+
+ # 对每个小组内的成员按姓名排序
+ for group_name in group_data:
+ group_data[group_name] = sorted(group_data[group_name], key=lambda x: x.get("name", ""))
+
+ # 创建一个特殊的学生列表,用于显示小组信息
+ filtered_students = []
+ for group_name in sorted(group_data.keys()):
+ # 添加一个表示小组的特殊条目
+ group_info = {
+ "id": f"GROUP_{group_name}",
+ "name": f"小组 {group_name}",
+ "gender": "",
+ "group": group_name,
+ "exist": True,
+ "is_group": True, # 标记这是一个小组
+ "members": group_data[group_name] # 保存小组成员列表
+ }
+ filtered_students.append(group_info)
+ elif group_index > 1 and sorted_groups:
+ # 选择特定小组 (索引从2开始,因为0是全部,1是全部小组)
+ group_index_adjusted = group_index - 2
+ if 0 <= group_index_adjusted < len(sorted_groups):
+ selected_group = sorted_groups[group_index_adjusted]
+ filtered_students = [
+ student for student in students_dict_list
+ if "group" in student and student["group"] == selected_group
+ ]
+
+ # 根据性别筛选
+ if gender_index > 0: # 0表示全部性别
+ # 获取所有可用的性别
+ genders = set()
+ for student in filtered_students:
+ if student["gender"]:
+ genders.add(student["gender"])
+
+ # 将性别转换为排序后的列表
+ sorted_genders = sorted(list(genders))
+
+ # 根据索引获取选择的性别
+ if gender_index <= len(sorted_genders):
+ selected_gender = sorted_genders[gender_index - 1]
+ filtered_students = [
+ student for student in filtered_students
+ if student["gender"] == selected_gender
+ ]
+
+ # 根据half_repeat设置获取未抽取的学生
+ if self.half_repeat > 0:
+ # 读取已抽取记录
+ drawn_records = read_drawn_record(self.class_name, self.gender_filter, self.group_filter)
+ drawn_counts = {name: count for name, count in drawn_records}
+
+ # 过滤掉已抽取次数达到或超过设置值的学生
+ remaining_students = []
+
+ # 特殊处理小组模式 (group_index == 1)
+ if group_index == 1:
+ # 对于小组模式,需要检查每个小组是否还有未被完全抽取的成员
+ for student in filtered_students:
+ # 只处理小组条目
+ if student.get("is_group", False):
+ group_name = student["group"]
+ members = student.get("members", [])
+
+ # 检查小组成员是否都已被抽取
+ all_members_drawn = True
+ for member in members:
+ member_name = member["name"]
+ # 如果有成员未被抽取或抽取次数小于设置值,则小组保留
+ if (
+ member_name not in drawn_counts
+ or drawn_counts[member_name] < self.half_repeat
+ ):
+ all_members_drawn = False
+ break
+
+ # 只有当小组不是所有成员都被抽取时才保留
+ if not all_members_drawn:
+ remaining_students.append(student)
+ # 如果当前剩余人数等于零,则显示所有小组
+ elif count is not None and count == 0:
+ remaining_students.append(student)
+ else:
+ # 非小组条目,按原逻辑处理
+ student_name = student["name"]
+ if (
+ student_name not in drawn_counts
+ or drawn_counts[student_name] < self.half_repeat
+ ):
+ remaining_students.append(student)
+ elif count is not None and count == 0:
+ remaining_students.append(student)
+ else:
+ # 非小组模式,按原逻辑处理
+ for student in filtered_students:
+ student_name = student["name"]
+ # 如果学生未被抽取过,或者抽取次数小于设置值,则保留该学生
+ if (
+ student_name not in drawn_counts
+ or drawn_counts[student_name] < self.half_repeat
+ ):
+ remaining_students.append(student)
+ # 如果当前剩余人数等于零,则显示全部学生
+ elif count is not None and count == 0:
+ remaining_students.append(student)
+ else:
+ # 如果half_repeat为0,则所有学生都显示
+ remaining_students = filtered_students
+
+ self.students = remaining_students
+
+ # 使用QTimer延迟更新UI,避免在数据处理过程中直接更新UI
+ QTimer.singleShot(10, self.update_ui)
+ finally:
+ # 清除更新标志
+ self._updating = False
+
+ def update_remaining_list(
+ self,
+ class_name: str,
+ group_filter: str,
+ gender_filter: str,
+ half_repeat: int = 0,
+ group_index: int = 0,
+ gender_index: int = 0,
+ emit_signal: bool = True,
+ ):
+ """更新剩余名单
+
+ Args:
+ class_name: 班级名称
+ group_filter: 分组筛选条件
+ gender_filter: 性别筛选条件
+ half_repeat: 重复抽取次数
+ group_index: 分组索引
+ gender_index: 性别索引
+ emit_signal: 是否发出信号
+ """
+ # 更新属性
+ self.class_name = class_name
+ self.group_filter = group_filter
+ self.gender_filter = gender_filter
+ self.half_repeat = half_repeat
+ self.group_index = group_index
+ self.gender_index = gender_index
+
+ # 重置布局状态,强制更新
+ self._last_layout_width = 0
+ self._last_card_count = 0
+
+ # 重新加载学生数据
+ self.load_student_data()
+
+ # 如果需要发出信号,则发出count_changed信号
+ if emit_signal:
+ # 计算剩余人数
+ remaining_count = len(self.students) if hasattr(self, 'students') else 0
+ self.count_changed.emit(remaining_count)
+
+ def refresh(self):
+ """刷新页面"""
+ if self.class_name:
+ # 重置布局状态,强制更新
+ self._last_layout_width = 0
+ self._last_card_count = 0
+ self.load_student_data()
+
+ def on_count_changed(self, count):
+ """处理剩余人数变化的槽函数
+
+ Args:
+ count: 剩余人数
+ """
+ # 重新加载学生数据
+ self._load_and_update_students(count=count)
+
+ def resizeEvent(self, event):
+ """窗口大小变化事件"""
+ # 检查窗口大小是否真的改变了
+ new_size = event.size()
+ old_size = event.oldSize()
+
+ # 如果窗口大小没有改变,不触发布局更新
+ if new_size == old_size:
+ return
+
+ # 检查宽度是否发生了显著变化(至少变化5像素才触发布局更新)
+ width_change = abs(new_size.width() - self._last_layout_width)
+ if width_change < 5:
+ return
+
+ # 使用QTimer延迟布局更新,避免递归调用
+ if self._resize_timer is not None:
+ self._resize_timer.stop()
+ self._resize_timer = QTimer()
+ self._resize_timer.setSingleShot(True)
+ self._resize_timer.timeout.connect(self._delayed_update_layout)
+ self._resize_timer.start(100) # 增加延迟时间,减少频繁更新
+ super().resizeEvent(event)
+
+ def _delayed_update_layout(self):
+ """延迟更新布局"""
+ try:
+ if hasattr(self, "grid_layout") and self.grid_layout is not None:
+ if self.isVisible():
+ # 检查是否需要更新布局
+ current_width = self.width()
+ current_card_count = len(self.cards)
+
+ # 只有当宽度或卡片数量发生变化时才更新布局
+ if (current_width != self._last_layout_width or
+ current_card_count != self._last_card_count):
+ self.update_layout()
+ logger.debug(f"延迟布局更新完成,当前卡片数量: {len(self.cards)}")
+ else:
+ logger.debug(f"跳过布局更新: 宽度={current_width}, 卡片数={current_card_count}")
+ except RuntimeError as e:
+ logger.error(f"延迟布局更新错误: {e}")
+
+ def closeEvent(self, event):
+ """窗口关闭事件"""
+ # 清理定时器
+ if self._resize_timer is not None:
+ self._resize_timer.stop()
+ self._resize_timer = None
+
+ super().closeEvent(event)
\ No newline at end of file
diff --git a/app/view/another_window/set_class_name.py b/app/view/another_window/set_class_name.py
new file mode 100644
index 00000000..5ebee2da
--- /dev/null
+++ b/app/view/another_window/set_class_name.py
@@ -0,0 +1,387 @@
+# ==================================================
+# 导入库
+# ==================================================
+import re
+import json
+
+from loguru import logger
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from qfluentwidgets import *
+
+from app.tools.variable import *
+from app.tools.path_utils import *
+from app.tools.personalised import *
+from app.tools.settings_default import *
+from app.tools.settings_access import *
+from app.Language.obtain_language import *
+from app.tools.config import *
+from app.tools.list import *
+
+
+class SetClassNameWindow(QWidget):
+ """班级名称设置窗口"""
+
+ def __init__(self, parent=None):
+ """初始化班级名称设置窗口"""
+ super().__init__(parent)
+
+ # 初始化变量
+ self.saved = False
+ self.initial_class_names = [] # 保存初始加载的班级列表
+
+ # 初始化UI
+ self.init_ui()
+
+ # 连接信号
+ self.__connect_signals()
+
+ def init_ui(self):
+ """初始化UI"""
+ # 设置窗口标题
+ self.setWindowTitle(get_content_name_async("set_class_name", "title"))
+
+ # 创建主布局
+ self.main_layout = QVBoxLayout(self)
+ self.main_layout.setContentsMargins(20, 20, 20, 20)
+ self.main_layout.setSpacing(15)
+
+ # 创建标题
+ self.title_label = TitleLabel(get_content_name_async("set_class_name", "title"))
+ self.main_layout.addWidget(self.title_label)
+
+ # 创建说明标签
+ self.description_label = BodyLabel(
+ get_content_name_async("set_class_name", "description")
+ )
+ self.description_label.setWordWrap(True)
+ self.main_layout.addWidget(self.description_label)
+
+ # 创建班级名称输入区域
+ self.__create_class_name_input_area()
+
+ # 创建按钮区域
+ self.__create_button_area()
+
+ # 添加伸缩项
+ self.main_layout.addStretch(1)
+
+ def __create_class_name_input_area(self):
+ """创建班级名称输入区域"""
+ # 创建卡片容器
+ input_card = CardWidget()
+ input_layout = QVBoxLayout(input_card)
+
+ # 创建输入区域标题
+ input_title = SubtitleLabel(
+ get_content_name_async("set_class_name", "input_title")
+ )
+ input_layout.addWidget(input_title)
+
+ # 创建文本编辑框
+ self.text_edit = PlainTextEdit()
+ self.text_edit.setPlaceholderText(
+ get_content_name_async("set_class_name", "input_placeholder")
+ )
+
+ # 加载现有班级名称
+ try:
+ class_names = get_class_name_list()
+ if class_names:
+ self.text_edit.setPlainText("\n".join(class_names))
+ self.initial_class_names = class_names.copy() # 保存初始班级列表
+ except Exception as e:
+ logger.error(f"加载班级名称失败: {str(e)}")
+ self.initial_class_names = [] # 出错时设为空列表
+
+ input_layout.addWidget(self.text_edit)
+
+ # 添加到主布局
+ self.main_layout.addWidget(input_card)
+
+ def __create_button_area(self):
+ """创建按钮区域"""
+ # 创建按钮布局
+ button_layout = QHBoxLayout()
+
+ # 伸缩项
+ button_layout.addStretch(1)
+
+ # 保存按钮
+ self.save_button = PrimaryPushButton(
+ get_content_name_async("set_class_name", "save_button")
+ )
+ self.save_button.setIcon(FluentIcon.SAVE)
+ button_layout.addWidget(self.save_button)
+
+ # 取消按钮
+ self.cancel_button = PushButton(
+ get_content_name_async("set_class_name", "cancel_button")
+ )
+ self.cancel_button.setIcon(FluentIcon.CANCEL)
+ button_layout.addWidget(self.cancel_button)
+
+ # 添加到主布局
+ self.main_layout.addLayout(button_layout)
+
+ def __connect_signals(self):
+ """连接信号与槽"""
+ self.save_button.clicked.connect(self.__save_class_names)
+ self.cancel_button.clicked.connect(self.__cancel)
+ self.text_edit.textChanged.connect(self.__on_text_changed) # 添加文本变化监听
+
+ def __on_text_changed(self):
+ """检测文本变化,提示班级消失"""
+ try:
+ # 获取当前文本编辑框中的班级名称
+ current_text = self.text_edit.toPlainText().strip()
+ current_class_names = (
+ [name.strip() for name in current_text.split("\n") if name.strip()]
+ if current_text
+ else []
+ )
+
+ # 检查是否有班级消失(存在于初始列表但不存在于当前列表)
+ disappeared_classes = [
+ name
+ for name in self.initial_class_names
+ if name not in current_class_names
+ ]
+
+ # 如果有班级消失,显示提示
+ if disappeared_classes:
+ if len(disappeared_classes) == 1:
+ # 单个班级消失
+ message = get_content_name_async(
+ "set_class_name", "class_disappeared_message"
+ ).format(class_name=disappeared_classes[0])
+ else:
+ # 多个班级消失
+ message = get_content_name_async(
+ "set_class_name", "multiple_classes_disappeared_message"
+ ).format(
+ count=len(disappeared_classes),
+ class_names="\n".join(disappeared_classes),
+ )
+
+ # 显示提示
+ config = NotificationConfig(
+ title=get_content_name_async(
+ "set_class_name", "class_disappeared_title"
+ ),
+ content=message,
+ duration=3000,
+ )
+ show_notification(NotificationType.WARNING, config, parent=self)
+
+ # 更新初始班级列表为当前列表,避免重复提示
+ self.initial_class_names = current_class_names.copy()
+ except Exception as e:
+ logger.error(f"检测班级变化失败: {e}")
+
+ def __save_class_names(self):
+ """保存班级名称"""
+ try:
+ # 获取输入的班级名称
+ class_names_text = self.text_edit.toPlainText().strip()
+ if not class_names_text:
+ # 显示错误消息
+ config = NotificationConfig(
+ title=get_content_name_async("set_class_name", "error_title"),
+ content=get_content_name_async(
+ "set_class_name", "no_class_names_error"
+ ),
+ duration=3000,
+ )
+ show_notification(NotificationType.ERROR, config, parent=self)
+ return
+
+ # 分割班级名称
+ class_names = [
+ name.strip() for name in class_names_text.split("\n") if name.strip()
+ ]
+
+ # 验证班级名称
+ invalid_names = []
+ for name in class_names:
+ # 检查是否包含非法字符
+ if re.search(r'[\/:*?"<>|]', name):
+ invalid_names.append(name)
+ # 检查是否为保留字
+ elif name.lower() == "class":
+ invalid_names.append(name)
+
+ if invalid_names:
+ # 显示错误消息
+ config = NotificationConfig(
+ title=get_content_name_async("set_class_name", "error_title"),
+ content=get_content_name_async(
+ "set_class_name", "invalid_names_error"
+ ).format(names=", ".join(invalid_names)),
+ duration=5000,
+ )
+ show_notification(NotificationType.ERROR, config, parent=self)
+ return
+
+ # 获取班级名单目录
+ roll_call_list_dir = get_path("app/resources/list/roll_call_list")
+ roll_call_list_dir.mkdir(parents=True, exist_ok=True)
+
+ # 获取现有的班级名称
+ existing_class_names = get_class_name_list()
+
+ # 检查是否有被删除的班级
+ deleted_classes = [
+ name for name in existing_class_names if name not in class_names
+ ]
+
+ # 如果有被删除的班级,询问用户是否确认删除
+ if deleted_classes:
+ # 创建确认对话框
+ if len(deleted_classes) == 1:
+ # 单个班级删除
+ dialog = MessageBox(
+ get_content_name_async("set_class_name", "delete_class_title"),
+ get_content_name_async(
+ "set_class_name", "delete_class_message"
+ ).format(class_name=deleted_classes[0]),
+ self,
+ )
+
+ dialog.yesButton.setText(
+ get_content_name_async("set_class_name", "delete_class_button")
+ )
+ dialog.cancelButton.setText(
+ get_content_name_async("set_class_name", "delete_cancel_button")
+ )
+ else:
+ # 多个班级删除
+ dialog = MessageBox(
+ get_content_name_async(
+ "set_class_name", "delete_multiple_classes_title"
+ ),
+ get_content_name_async(
+ "set_class_name", "delete_multiple_classes_message"
+ ).format(
+ count=len(deleted_classes),
+ class_names="\n".join(deleted_classes),
+ ),
+ self,
+ )
+
+ dialog.yesButton.setText(
+ get_content_name_async("set_class_name", "delete_class_button")
+ )
+ dialog.cancelButton.setText(
+ get_content_name_async("set_class_name", "delete_cancel_button")
+ )
+
+ # 显示对话框并获取用户选择
+ if dialog.exec():
+ # 用户确认删除,删除班级文件
+ deleted_count = 0
+ for class_name in deleted_classes:
+ class_file = roll_call_list_dir / f"{class_name}.json"
+ if class_file.exists():
+ class_file.unlink()
+ deleted_count += 1
+
+ # 显示删除成功消息
+ if deleted_count > 0:
+ config = NotificationConfig(
+ title=get_content_name_async(
+ "set_class_name", "delete_success_title"
+ ),
+ content=get_content_name_async(
+ "set_class_name", "delete_success_message"
+ ).format(count=deleted_count),
+ duration=3000,
+ )
+ show_notification(NotificationType.SUCCESS, config, parent=self)
+ else:
+ # 用户取消删除,不执行任何操作
+ return
+
+ # 创建或更新班级文件
+ created_count = 0
+ for class_name in class_names:
+ class_file = roll_call_list_dir / f"{class_name}.json"
+ if not class_file.exists():
+ # 创建空的班级文件
+ with open_file(class_file, "w", encoding="utf-8") as f:
+ json.dump({}, f, ensure_ascii=False, indent=4)
+ created_count += 1
+
+ # 显示成功消息
+ if created_count > 0:
+ config = NotificationConfig(
+ title=get_content_name_async("set_class_name", "success_title"),
+ content=get_content_name_async(
+ "set_class_name", "success_message"
+ ).format(count=created_count),
+ duration=3000,
+ )
+ show_notification(NotificationType.SUCCESS, config, parent=self)
+ elif not deleted_classes:
+ # 没有创建新班级也没有删除班级
+ config = NotificationConfig(
+ title=get_content_name_async("set_class_name", "info_title"),
+ content=get_content_name_async(
+ "set_class_name", "no_new_classes_message"
+ ),
+ duration=3000,
+ )
+ show_notification(NotificationType.INFO, config, parent=self)
+
+ # 标记为已保存
+ self.saved = True
+
+ except Exception as e:
+ # 显示错误消息
+ config = NotificationConfig(
+ title=get_content_name_async("set_class_name", "error_title"),
+ content=f"{get_content_name_async('set_class_name', 'save_error')}: {str(e)}",
+ duration=3000,
+ )
+ show_notification(NotificationType.ERROR, config, parent=self)
+ logger.error(f"保存班级名称失败: {e}")
+
+ def __cancel(self):
+ """取消操作"""
+ # 获取父窗口并关闭
+ parent = self.parent()
+ while parent:
+ # 查找SimpleWindowTemplate类型的父窗口
+ if hasattr(parent, "windowClosed") and hasattr(parent, "close"):
+ parent.close()
+ break
+ parent = parent.parent()
+
+ def closeEvent(self, event):
+ """窗口关闭事件处理"""
+ if not self.saved:
+ # 创建确认对话框
+ dialog = Dialog(
+ get_content_name_async("set_class_name", "unsaved_changes_title"),
+ get_content_name_async("set_class_name", "unsaved_changes_message"),
+ self,
+ )
+
+ dialog.yesButton.setText(
+ get_content_name_async("set_class_name", "discard_button")
+ )
+ dialog.cancelButton.setText(
+ get_content_name_async("set_class_name", "continue_editing_button")
+ )
+
+ # 显示对话框并获取用户选择
+ if dialog.exec():
+ # 用户选择放弃更改,关闭窗口
+ event.accept()
+ else:
+ # 用户选择继续编辑,取消关闭事件
+ event.ignore()
+ else:
+ # 已保存,直接关闭
+ event.accept()
diff --git a/app/view/main/__init__.py b/app/view/main/__init__.py
new file mode 100644
index 00000000..a7df34dc
--- /dev/null
+++ b/app/view/main/__init__.py
@@ -0,0 +1 @@
+"""Main window views package."""
diff --git a/app/view/main/roll_call.py b/app/view/main/roll_call.py
index 66257251..64d29d34 100644
--- a/app/view/main/roll_call.py
+++ b/app/view/main/roll_call.py
@@ -2,15 +2,11 @@
# 导入库
# ==================================================
import json
-import os
-import sys
-import subprocess
-
-from loguru import logger
-from PyQt6.QtWidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
+
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from PySide6.QtNetwork import *
from qfluentwidgets import *
from app.tools.variable import *
@@ -23,6 +19,9 @@
from app.tools.list import *
from app.tools.history import *
from app.tools.result_display import *
+from app.tools.config import *
+
+from app.page_building.another_window import *
from random import SystemRandom
system_random = SystemRandom()
@@ -33,16 +32,63 @@
class roll_call(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
+ self.file_watcher = QFileSystemWatcher()
+ self.setup_file_watcher()
+
+ # 长按功能相关变量
+ self.press_timer = QTimer()
+ self.press_timer.timeout.connect(self.handle_long_press)
+ self.long_press_interval = 100 # 长按时连续触发的间隔时间(毫秒)
+ self.long_press_delay = 500 # 开始长按前的延迟时间(毫秒)
+ self.is_long_pressing = False # 是否正在长按
+ self.long_press_direction = 0 # 长按方向:1为增加,-1为减少
+
self.initUI()
+ def handle_long_press(self):
+ """处理长按事件"""
+ if self.is_long_pressing:
+ # 更新定时器间隔为连续触发间隔
+ self.press_timer.setInterval(self.long_press_interval)
+ # 执行更新计数
+ self.update_count(self.long_press_direction)
+
+ def start_long_press(self, direction):
+ """开始长按
+
+ Args:
+ direction (int): 长按方向,1为增加,-1为减少
+ """
+ self.long_press_direction = direction
+ self.is_long_pressing = True
+ # 设置初始延迟
+ self.press_timer.setInterval(self.long_press_delay)
+ self.press_timer.start()
+
+ def stop_long_press(self):
+ """停止长按"""
+ self.is_long_pressing = False
+ self.press_timer.stop()
+
+ def closeEvent(self, event):
+ """窗口关闭事件,清理资源"""
+ try:
+ if hasattr(self, "file_watcher"):
+ self.file_watcher.removePaths(self.file_watcher.directories())
+ self.file_watcher.removePaths(self.file_watcher.files())
+ # 停止长按定时器
+ if hasattr(self, "press_timer"):
+ self.press_timer.stop()
+ except Exception as e:
+ logger.error(f"清理文件监控器失败: {e}")
+ super().closeEvent(event)
+
def initUI(self):
"""初始化UI"""
- # 主容器
container = QWidget()
roll_call_container = QVBoxLayout(container)
roll_call_container.setContentsMargins(0, 0, 0, 0)
-
- # 结果显示区域
+
self.result_widget = QWidget()
self.result_layout = QVBoxLayout(self.result_widget)
self.result_grid = QGridLayout()
@@ -51,91 +97,891 @@ def initUI(self):
self.result_layout.addStretch()
roll_call_container.addWidget(self.result_widget)
- # 控制面板
- self.start_button = PrimaryPushButton('开始')
+ self.reset_button = PushButton(
+ get_content_pushbutton_name_async("roll_call", "reset_button")
+ )
+ self.reset_button.setFont(QFont(load_custom_font(), 15))
+ self.reset_button.setFixedSize(165, 45)
+ self.reset_button.clicked.connect(lambda: self.reset_count())
+
+ self.minus_button = PushButton("-")
+ self.minus_button.setFont(QFont(load_custom_font(), 20))
+ self.minus_button.setFixedSize(45, 45)
+ self.minus_button.clicked.connect(lambda: self.update_count(-1))
+
+ # 添加长按连续减功能
+ self.minus_button.pressed.connect(lambda: self.start_long_press(-1))
+ self.minus_button.released.connect(self.stop_long_press)
+
+ self.count_label = BodyLabel("1")
+ self.count_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
+ self.count_label.setFont(QFont(load_custom_font(), 20))
+ self.count_label.setFixedSize(65, 45)
+ self.current_count = 1
+
+ self.plus_button = PushButton("+")
+ self.plus_button.setFont(QFont(load_custom_font(), 20))
+ self.plus_button.setFixedSize(45, 45)
+ self.plus_button.clicked.connect(lambda: self.update_count(1))
+
+ # 添加长按连续加功能
+ self.plus_button.pressed.connect(lambda: self.start_long_press(1))
+ self.plus_button.released.connect(self.stop_long_press)
+
+ self.minus_button.setEnabled(False)
+ self.plus_button.setEnabled(True)
+
+ count_widget = QWidget()
+ horizontal_layout = QHBoxLayout()
+ horizontal_layout.setContentsMargins(0, 0, 0, 0)
+ horizontal_layout.addWidget(self.minus_button, 0, Qt.AlignmentFlag.AlignLeft)
+ horizontal_layout.addWidget(self.count_label, 0, Qt.AlignmentFlag.AlignLeft)
+ horizontal_layout.addWidget(self.plus_button, 0, Qt.AlignmentFlag.AlignLeft)
+ count_widget.setLayout(horizontal_layout)
+
+ self.start_button = PrimaryPushButton(
+ get_content_pushbutton_name_async("roll_call", "start_button")
+ )
+ self.start_button.setFont(QFont(load_custom_font(), 15))
+ self.start_button.setFixedSize(165, 45)
self.start_button.clicked.connect(lambda: self.start_draw())
- # 右侧控制区
+ self.list_combobox = ComboBox()
+ self.list_combobox.setFont(QFont(load_custom_font(), 12))
+ self.list_combobox.setFixedSize(165, 45)
+ # 延迟填充班级列表,避免启动时进行文件IO
+ self.list_combobox.currentTextChanged.connect(self.on_class_changed)
+
+ self.range_combobox = ComboBox()
+ self.range_combobox.setFont(QFont(load_custom_font(), 12))
+ self.range_combobox.setFixedSize(165, 45)
+ # 延迟填充范围选项
+ self.range_combobox.currentTextChanged.connect(self.on_filter_changed)
+
+ self.gender_combobox = ComboBox()
+ self.gender_combobox.setFont(QFont(load_custom_font(), 12))
+ self.gender_combobox.setFixedSize(165, 45)
+ # 延迟填充性别选项
+ self.gender_combobox.currentTextChanged.connect(self.on_filter_changed)
+
+ self.remaining_button = PushButton(
+ get_content_pushbutton_name_async("roll_call", "remaining_button")
+ )
+ self.remaining_button.setFont(QFont(load_custom_font(), 12))
+ self.remaining_button.setFixedSize(165, 45)
+ self.remaining_button.clicked.connect(lambda: self.show_remaining_list())
+
+ # 初始时不进行昂贵的数据加载,改为延迟填充
+ self.total_count = 0
+ self.remaining_count = 0
+
+ text_template = get_any_position_value(
+ "roll_call", "many_count_label", "text_0"
+ )
+ # 使用占位值,实际文本将在 populate_lists 中更新
+ formatted_text = text_template.format(total_count=0, remaining_count=0)
+ self.many_count_label = BodyLabel(formatted_text)
+ self.many_count_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
+ self.many_count_label.setFont(QFont(load_custom_font(), 10))
+ self.many_count_label.setFixedWidth(165)
+
control_widget = QWidget()
control_layout = QVBoxLayout(control_widget)
- control_layout.setContentsMargins(10, 10, 10, 10)
+ control_layout.setContentsMargins(0, 0, 0, 0)
control_layout.addStretch()
- control_layout.addWidget(self.start_button, alignment=Qt.AlignmentFlag.AlignCenter)
+ control_layout.addWidget(
+ self.reset_button, alignment=Qt.AlignmentFlag.AlignCenter
+ )
+ control_layout.addWidget(count_widget, alignment=Qt.AlignmentFlag.AlignCenter)
+ control_layout.addWidget(
+ self.start_button, alignment=Qt.AlignmentFlag.AlignCenter
+ )
+ control_layout.addWidget(
+ self.list_combobox, alignment=Qt.AlignmentFlag.AlignCenter
+ )
+ control_layout.addWidget(
+ self.range_combobox, alignment=Qt.AlignmentFlag.AlignCenter
+ )
+ control_layout.addWidget(
+ self.gender_combobox, alignment=Qt.AlignmentFlag.AlignCenter
+ )
+ control_layout.addWidget(
+ self.remaining_button, alignment=Qt.AlignmentFlag.AlignCenter
+ )
+ control_layout.addWidget(
+ self.many_count_label, alignment=Qt.AlignmentFlag.AlignCenter
+ )
- # 滚动区
scroll = SmoothScrollArea()
scroll.setWidget(container)
scroll.setWidgetResizable(True)
- # 主布局
main_layout = QHBoxLayout(self)
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.addWidget(scroll, 1)
main_layout.addWidget(control_widget)
+ # 在事件循环中延迟填充下拉框和初始统计,减少启动阻塞
+ QTimer.singleShot(0, self.populate_lists)
+
+ def on_class_changed(self):
+ """当班级选择改变时,更新范围选择、性别选择和人数显示"""
+ self.range_combobox.blockSignals(True)
+ self.gender_combobox.blockSignals(True)
+
+ try:
+ self.range_combobox.clear()
+ # 范围
+ base_options = get_content_combo_name_async("roll_call", "range_combobox")
+ group_list = get_group_list(self.list_combobox.currentText())
+ # 如果有小组,才添加"抽取全部小组"选项
+ if group_list and group_list != [""]:
+ # 添加基础选项和小组列表
+ self.range_combobox.addItems(base_options + group_list)
+ else:
+ # 只添加基础选项,跳过"抽取全部小组"
+ self.range_combobox.addItems(base_options[:1]) # 只添加"抽取全部学生"
+
+ # 性别
+ self.gender_combobox.clear()
+ gender_options = get_content_combo_name_async("roll_call", "gender_combobox")
+ gender_list = get_gender_list(self.list_combobox.currentText())
+ # 如果有性别,才添加"抽取全部性别"选项
+ if gender_list and gender_list != [""]:
+ # 添加基础选项和性别列表
+ self.gender_combobox.addItems(gender_options + gender_list)
+ else:
+ # 只添加基础选项,跳过"抽取全部性别"
+ self.gender_combobox.addItems(gender_options[:1]) # 只添加"抽取全部性别"
+
+ # 根据当前选择的范围计算实际的总人数
+ group_index = self.range_combobox.currentIndex()
+ group_filter = self.range_combobox.currentText()
+
+ # 使用统一的方法更新剩余人数显示
+ self.update_many_count_label()
+
+ # 获取当前选择的小组/性别
+ group_index = self.range_combobox.currentIndex()
+
+ # 根据范围计算实际人数
+ if group_index == 0: # 全班
+ total_count = len(get_student_list(self.list_combobox.currentText()))
+ elif group_index == 1: # 小组模式 - 计算小组数量
+ total_count = len(get_group_list(self.list_combobox.currentText()))
+ else: # 特定小组 - 计算该小组的学生数量
+ group_filter = self.range_combobox.currentText()
+ students = get_student_list(self.list_combobox.currentText())
+ total_count = len([s for s in students if s["group"] == group_filter])
+
+ # 根据总人数是否为0,启用或禁用开始按钮
+ if total_count == 0:
+ self.start_button.setEnabled(False)
+ else:
+ self.start_button.setEnabled(True)
+
+ # 根据总人数是否为0,启用或禁用开始按钮
+ if total_count == 0:
+ self.start_button.setEnabled(False)
+ else:
+ self.start_button.setEnabled(True)
+
+ # 更新剩余名单窗口
+ if (
+ hasattr(self, "remaining_list_page")
+ and self.remaining_list_page is not None
+ ):
+ QTimer.singleShot(100, self._update_remaining_list_delayed)
+ except Exception as e:
+ logger.error(f"切换班级时发生错误: {e}")
+ finally:
+ self.range_combobox.blockSignals(False)
+ self.gender_combobox.blockSignals(False)
+
+ def on_filter_changed(self):
+ """当范围或性别选择改变时,更新人数显示"""
+ try:
+ # 使用统一的方法更新剩余人数显示
+ self.update_many_count_label()
+
+ # 获取当前选择的小组/性别
+ group_index = self.range_combobox.currentIndex()
+
+ # 根据范围计算实际人数
+ if group_index == 0: # 全班
+ total_count = len(get_student_list(self.list_combobox.currentText()))
+ elif group_index == 1: # 小组模式 - 计算小组数量
+ total_count = len(get_group_list(self.list_combobox.currentText()))
+ else: # 特定小组 - 计算该小组的学生数量
+ group_filter = self.range_combobox.currentText()
+ students = get_student_list(self.list_combobox.currentText())
+ total_count = len([s for s in students if s["group"] == group_filter])
+
+ # 根据总人数是否为0,启用或禁用开始按钮
+ if total_count == 0:
+ self.start_button.setEnabled(False)
+ else:
+ self.start_button.setEnabled(True)
+
+ if (
+ hasattr(self, "remaining_list_page")
+ and self.remaining_list_page is not None
+ ):
+ QTimer.singleShot(100, self._update_remaining_list_delayed)
+ except Exception as e:
+ logger.error(f"切换筛选条件时发生错误: {e}")
+
+ def _update_remaining_list_delayed(self):
+ """延迟更新剩余名单窗口的方法"""
+ try:
+ if (
+ hasattr(self, "remaining_list_page")
+ and self.remaining_list_page is not None
+ ):
+ class_name = self.list_combobox.currentText()
+ group_filter = self.range_combobox.currentText()
+ gender_filter = self.gender_combobox.currentText()
+ group_index = self.range_combobox.currentIndex()
+ gender_index = self.gender_combobox.currentIndex()
+ half_repeat = readme_settings_async("roll_call_settings", "half_repeat")
+
+ if hasattr(self.remaining_list_page, "update_remaining_list"):
+ self.remaining_list_page.update_remaining_list(
+ class_name,
+ group_filter,
+ gender_filter,
+ half_repeat,
+ group_index,
+ gender_index,
+ emit_signal=False,
+ )
+ else:
+ if hasattr(self.remaining_list_page, "class_name"):
+ self.remaining_list_page.class_name = class_name
+ if hasattr(self.remaining_list_page, "group_filter"):
+ self.remaining_list_page.group_filter = group_filter
+ if hasattr(self.remaining_list_page, "gender_filter"):
+ self.remaining_list_page.gender_filter = gender_filter
+ if hasattr(self.remaining_list_page, "group_index"):
+ self.remaining_list_page.group_index = group_index
+ if hasattr(self.remaining_list_page, "gender_index"):
+ self.remaining_list_page.gender_index = gender_index
+ if hasattr(self.remaining_list_page, "half_repeat"):
+ self.remaining_list_page.half_repeat = half_repeat
+
+ if hasattr(self.remaining_list_page, "count_changed"):
+ self.remaining_list_page.count_changed.emit(
+ self.remaining_count
+ )
+ except Exception as e:
+ logger.error(f"延迟更新剩余名单时发生错误: {e}")
+
def start_draw(self):
"""开始抽取"""
+ self.start_button.setText(
+ get_content_pushbutton_name_async("roll_call", "start_button")
+ )
+ self.start_button.setEnabled(True)
+ try:
+ self.start_button.clicked.disconnect()
+ except:
+ pass
+ self.draw_random()
animation = readme_settings_async("roll_call_settings", "animation")
autoplay_count = readme_settings_async("roll_call_settings", "autoplay_count")
- animation_interval = readme_settings_async("roll_call_settings", "animation_interval")
- if animation == 0: # 手动停止动画
- self.start_button.setText("停止")
+ animation_interval = readme_settings_async(
+ "roll_call_settings", "animation_interval"
+ )
+ if animation == 0:
+ self.start_button.setText(
+ get_content_pushbutton_name_async("roll_call", "stop_button")
+ )
self.is_animating = True
self.animation_timer = QTimer()
- self.animation_timer.timeout.connect(self.draw_random)
+ self.animation_timer.timeout.connect(self.animate_result)
self.animation_timer.start(animation_interval)
- self.start_button.clicked.disconnect()
self.start_button.clicked.connect(lambda: self.stop_animation())
- elif animation == 1: # 自动停止动画
+ elif animation == 1:
self.is_animating = True
self.animation_timer = QTimer()
- self.animation_timer.timeout.connect(self.draw_random)
+ self.animation_timer.timeout.connect(self.animate_result)
self.animation_timer.start(animation_interval)
self.start_button.setEnabled(False)
- QTimer.singleShot(autoplay_count * animation_interval, lambda: [
- self.animation_timer.stop(),
- lambda: self.stop_animation(),
- self.start_button.setEnabled(True)
- ])
- elif animation == 2: # 直接显示结果
- self.draw_random()
-
+ QTimer.singleShot(
+ autoplay_count * animation_interval,
+ lambda: [
+ self.animation_timer.stop(),
+ self.stop_animation(),
+ self.start_button.setEnabled(True),
+ ],
+ )
+ self.start_button.clicked.connect(lambda: self.start_draw())
+ elif animation == 2:
+ if hasattr(self, "final_selected_students") and hasattr(
+ self, "final_class_name"
+ ):
+ save_roll_call_history(
+ class_name=self.final_class_name,
+ selected_students=self.final_selected_students,
+ students_dict_list=self.final_students_dict_list,
+ group_filter=self.final_group_filter,
+ gender_filter=self.final_gender_filter,
+ )
+ self.start_button.clicked.connect(lambda: self.start_draw())
+
def stop_animation(self):
"""停止动画"""
- self.animation_timer.stop()
- self.start_button.setText("开始")
+ if hasattr(self, "animation_timer") and self.animation_timer.isActive():
+ self.animation_timer.stop()
+ self.start_button.setText(
+ get_content_pushbutton_name_async("roll_call", "start_button")
+ )
self.is_animating = False
- self.start_button.clicked.disconnect()
- self.start_button.clicked.connect(self.draw_random)
+ try:
+ self.start_button.clicked.disconnect()
+ except:
+ pass
+ self.start_button.clicked.connect(lambda: self.start_draw())
+
+ half_repeat = readme_settings_async("roll_call_settings", "half_repeat")
+ if half_repeat > 0:
+ record_drawn_student(
+ class_name=self.final_class_name,
+ gender=self.final_gender_filter,
+ group=self.final_group_filter,
+ student_name=self.final_selected_students,
+ )
+
+ self.update_many_count_label()
+
+ if (
+ hasattr(self, "remaining_list_page")
+ and self.remaining_list_page is not None
+ and hasattr(self.remaining_list_page, "count_changed")
+ ):
+ self.remaining_list_page.count_changed.emit(self.remaining_count)
+
+ # 更新剩余名单窗口
+ QTimer.singleShot(100, self._update_remaining_list_delayed)
+
+ if hasattr(self, "final_selected_students") and hasattr(
+ self, "final_class_name"
+ ):
+ save_roll_call_history(
+ class_name=self.final_class_name,
+ selected_students=self.final_selected_students_dict,
+ group_filter=self.final_group_filter,
+ gender_filter=self.final_gender_filter,
+ )
+
+ if hasattr(self, "final_selected_students"):
+ self.display_result(self.final_selected_students, self.final_class_name)
+
+ def animate_result(self):
+ """动画过程中更新显示"""
+ self.draw_random()
def draw_random(self):
"""抽取随机结果"""
- class_name = "测试-1"
- group_index = 0
- gender_index = 0
+ class_name = self.list_combobox.currentText()
+ group_index = self.range_combobox.currentIndex()
+ group_filter = self.range_combobox.currentText()
+ gender_index = self.gender_combobox.currentIndex()
+ gender_filter = self.gender_combobox.currentText()
student_file = get_resources_path("list/roll_call_list", f"{class_name}.json")
- with open_file(student_file, 'r', encoding='utf-8') as f:
+ with open_file(student_file, "r", encoding="utf-8") as f:
data = json.load(f)
- students_data = filter_students_data(data, group_index, gender_index)
+
+ students_data = filter_students_data(
+ data, group_index, group_filter, gender_index, gender_filter
+ )
if group_index == 1:
- students_data = sorted(students_data, key=lambda x: str(x))
- random_student = system_random.choice(students_data)
- id = random_student[0]
- random_name = random_student[1]
- exist = random_student[4]
- self.display_result(id, random_name, exist)
-
- def display_result(self, id, name, exist):
+ # 小组模式下,按小组名称排序
+ students_data = sorted(students_data, key=lambda x: x[3]) # x[3]是小组名称
+
+ # 首先将学生数据转换为字典列表
+ students_dict_list = []
+ for student_tuple in students_data:
+ student_dict = {
+ "id": student_tuple[0],
+ "name": student_tuple[1],
+ "gender": student_tuple[2],
+ "group": student_tuple[3],
+ "exist": student_tuple[4],
+ }
+ students_dict_list.append(student_dict)
+
+ # 获取抽取类型
+ draw_type = readme_settings_async("roll_call_settings", "draw_type")
+
+ # 处理小组模式下的特殊逻辑
+ if group_index == 1:
+ # 小组模式下,students_data已经只包含小组信息
+ # 直接使用小组数据进行抽取
+ draw_count = min(self.current_count, len(students_dict_list))
+
+ selected_groups = []
+ if draw_type == 1:
+ # 权重抽取模式下,所有小组权重相同
+ weights = [1.0] * len(students_dict_list)
+
+ # 根据权重抽取小组
+ for _ in range(draw_count):
+ if not students_dict_list:
+ break
+ total_weight = sum(weights)
+ if total_weight <= 0:
+ random_index = system_random.randint(0, len(students_dict_list) - 1)
+ else:
+ rand_value = system_random.uniform(0, total_weight)
+ cumulative_weight = 0
+ random_index = 0
+ for i, weight in enumerate(weights):
+ cumulative_weight += weight
+ if rand_value <= cumulative_weight:
+ random_index = i
+ break
+
+ selected_group = students_dict_list[random_index]
+ selected_groups.append((None, selected_group["name"], True)) # (id, name, exist)
+
+ students_dict_list.pop(random_index)
+ weights.pop(random_index)
+ else:
+ # 随机抽取模式
+ for _ in range(draw_count):
+ if not students_dict_list:
+ break
+ random_index = system_random.randint(0, len(students_dict_list) - 1)
+ selected_group = students_dict_list[random_index]
+ selected_groups.append((None, selected_group["name"], True)) # (id, name, exist)
+
+ students_dict_list.pop(random_index)
+
+ self.final_selected_students = selected_groups
+ self.final_class_name = class_name
+ self.final_selected_students_dict = [] # 小组模式下不存储学生字典
+ self.final_group_filter = group_filter
+ self.final_gender_filter = gender_filter
+
+ self.display_result(selected_groups, class_name)
+ return
+
+ half_repeat = readme_settings_async("roll_call_settings", "half_repeat")
+ if half_repeat > 0:
+ drawn_records = read_drawn_record(class_name, gender_filter, group_filter)
+ drawn_counts = {name: count for name, count in drawn_records}
+
+ filtered_students = []
+ for student in students_dict_list:
+ student_name = student["name"]
+ if (
+ student_name not in drawn_counts
+ or drawn_counts[student_name] < half_repeat
+ ):
+ filtered_students.append(student)
+
+ students_dict_list = filtered_students
+
+ if not students_dict_list:
+ reset_drawn_record(self, class_name, gender_filter, group_filter)
+
+ draw_type = readme_settings_async("roll_call_settings", "draw_type")
+ if draw_type == 1:
+ students_with_weight = calculate_weight(students_dict_list, class_name)
+ weights = []
+ for student in students_with_weight:
+ weights.append(student.get("weight", 1.0))
+ else:
+ students_with_weight = students_dict_list
+ weights = [1.0] * len(students_dict_list)
+
+ draw_count = self.current_count
+ draw_count = min(draw_count, len(students_with_weight))
+
+ selected_students = []
+ selected_students_dict = []
+ for _ in range(draw_count):
+ if not students_with_weight:
+ break
+ total_weight = sum(weights)
+ if total_weight <= 0:
+ random_index = system_random.randint(0, len(students_with_weight) - 1)
+ else:
+ rand_value = system_random.uniform(0, total_weight)
+ cumulative_weight = 0
+ random_index = 0
+ for i, weight in enumerate(weights):
+ cumulative_weight += weight
+ if rand_value <= cumulative_weight:
+ random_index = i
+ break
+
+ selected_student = students_with_weight[random_index]
+ id = selected_student.get("id", "")
+ random_name = selected_student.get("name", "")
+ exist = selected_student.get("exist", True)
+ selected_students.append((id, random_name, exist))
+ selected_students_dict.append(selected_student)
+
+ students_with_weight.pop(random_index)
+ weights.pop(random_index)
+
+ self.final_selected_students = selected_students
+ self.final_class_name = class_name
+ self.final_selected_students_dict = selected_students_dict
+ self.final_group_filter = group_filter
+ self.final_gender_filter = gender_filter
+
+ self.display_result(selected_students, class_name)
+
+ def display_result(self, selected_students, class_name):
"""显示抽取结果"""
- selected_students = [(id, name, exist)]
+ group_index = self.range_combobox.currentIndex()
student_labels = ResultDisplayUtils.create_student_label(
+ class_name=class_name,
selected_students=selected_students,
- draw_count=1,
+ draw_count=self.current_count,
font_size=readme_settings_async("roll_call_settings", "font_size"),
- animation_color=readme_settings_async("roll_call_settings", "animation_color_theme"),
- display_format=readme_settings_async("roll_call_settings", "display_format"),
- show_student_image=readme_settings_async("roll_call_settings", "student_image"),
- group_index=0
+ animation_color=readme_settings_async(
+ "roll_call_settings", "animation_color_theme"
+ ),
+ display_format=readme_settings_async(
+ "roll_call_settings", "display_format"
+ ),
+ show_student_image=readme_settings_async(
+ "roll_call_settings", "student_image"
+ ),
+ group_index=group_index,
+ show_random=readme_settings_async(
+ "roll_call_settings", "show_random"
+ ),
+ )
+ ResultDisplayUtils.display_results_in_grid(self.result_grid, student_labels)
+
+ def reset_count(self):
+ """重置人数"""
+ self.current_count = 1
+ self.count_label.setText("1")
+ self.minus_button.setEnabled(False)
+ self.plus_button.setEnabled(True)
+ class_name = self.list_combobox.currentText()
+ gender = self.gender_combobox.currentText()
+ group = self.range_combobox.currentText()
+ reset_drawn_record(self, class_name, gender, group)
+ self.clear_result()
+ self.update_many_count_label()
+
+ # 更新剩余名单窗口
+ if (
+ hasattr(self, "remaining_list_page")
+ and self.remaining_list_page is not None
+ ):
+ QTimer.singleShot(100, self._update_remaining_list_delayed)
+
+ if (
+ hasattr(self, "remaining_list_page")
+ and self.remaining_list_page is not None
+ and hasattr(self.remaining_list_page, "count_changed")
+ ):
+ self.remaining_list_page.count_changed.emit(self.remaining_count)
+
+ def clear_result(self):
+ """清空结果显示"""
+ ResultDisplayUtils.clear_grid(self.result_grid)
+
+ def update_count(self, change):
+ """更新人数
+
+ Args:
+ change (int): 变化量,正数表示增加,负数表示减少
+ """
+ try:
+ self.total_count = self.get_total_count()
+ self.current_count = max(1, int(self.count_label.text()) + change)
+ self.count_label.setText(str(self.current_count))
+ self.minus_button.setEnabled(self.current_count > 1)
+ self.plus_button.setEnabled(self.current_count < self.total_count)
+ except (ValueError, TypeError):
+ self.count_label.setText("1")
+ self.minus_button.setEnabled(False)
+ self.plus_button.setEnabled(True)
+
+ def get_total_count(self):
+ """获取总人数"""
+ # 获取当前选择的范围和性别
+ group_index = self.range_combobox.currentIndex()
+ group_filter = self.range_combobox.currentText()
+
+ # 根据范围计算实际人数
+ if group_index == 0: # 全班
+ total_count = len(get_student_list(self.list_combobox.currentText()))
+ elif group_index == 1: # 小组模式 - 计算小组数量
+ total_count = len(get_group_list(self.list_combobox.currentText()))
+ else: # 特定小组 - 计算该小组的学生数量
+ students = get_student_list(self.list_combobox.currentText())
+ total_count = len([s for s in students if s["group"] == group_filter])
+ return total_count
+
+ def update_many_count_label(self):
+ """更新多数量显示标签"""
+ # 获取当前选择的小组/性别
+ group_index = self.range_combobox.currentIndex()
+ group_filter = self.range_combobox.currentText()
+ gender_filter = self.gender_combobox.currentText()
+
+ # 根据范围计算实际人数
+ if group_index == 0: # 全班
+ total_count = len(get_student_list(self.list_combobox.currentText()))
+ elif group_index == 1: # 小组模式 - 计算小组数量
+ total_count = len(get_group_list(self.list_combobox.currentText()))
+ else: # 特定小组 - 计算该小组的学生数量
+ students = get_student_list(self.list_combobox.currentText())
+ total_count = len([s for s in students if s["group"] == group_filter])
+
+ self.remaining_count = calculate_remaining_count(
+ half_repeat=readme_settings_async("roll_call_settings", "half_repeat"),
+ class_name=self.list_combobox.currentText(),
+ gender_filter=gender_filter,
+ group_index=group_index,
+ group_filter=group_filter,
+ total_count=total_count,
+ )
+ if self.remaining_count == 0:
+ self.remaining_count = total_count
+
+ # 根据是否为小组模式选择不同的文本模板
+ if group_index == 1: # 小组模式
+ text_template = get_any_position_value(
+ "roll_call", "many_count_label", "text_3"
+ )
+ else: # 学生模式
+ text_template = get_any_position_value(
+ "roll_call", "many_count_label", "text_0"
+ )
+ formatted_text = text_template.format(
+ total_count=total_count, remaining_count=self.remaining_count
+ )
+ self.many_count_label.setText(formatted_text)
+
+ # 根据总人数是否为0,启用或禁用开始按钮
+ if total_count == 0:
+ self.start_button.setEnabled(False)
+ else:
+ self.start_button.setEnabled(True)
+
+ def update_remaining_list_window(self):
+ """更新剩余名单窗口的内容"""
+ if hasattr(self, "remaining_list_page") and self.remaining_list_page is not None:
+ try:
+ class_name = self.list_combobox.currentText()
+ group_filter = self.range_combobox.currentText()
+ gender_filter = self.gender_combobox.currentText()
+ group_index = self.range_combobox.currentIndex()
+ gender_index = self.gender_combobox.currentIndex()
+ half_repeat = readme_settings_async("roll_call_settings", "half_repeat")
+
+ # 更新剩余名单页面内容
+ if hasattr(self.remaining_list_page, "update_remaining_list"):
+ self.remaining_list_page.update_remaining_list(
+ class_name,
+ group_filter,
+ gender_filter,
+ half_repeat,
+ group_index,
+ gender_index,
+ emit_signal=False, # 不发出信号,避免循环更新
+ )
+ except Exception as e:
+ logger.error(f"更新剩余名单窗口内容失败: {e}")
+
+ def show_remaining_list(self):
+ """显示剩余名单窗口"""
+ # 如果窗口已存在,则激活该窗口并更新内容
+ if hasattr(self, "remaining_list_page") and self.remaining_list_page is not None:
+ try:
+ # 获取窗口实例
+ window = self.remaining_list_page.window()
+ if window is not None:
+ # 激活窗口并置于前台
+ window.raise_()
+ window.activateWindow()
+ # 更新窗口内容
+ self.update_remaining_list_window()
+ return
+ except Exception as e:
+ logger.error(f"激活剩余名单窗口失败: {e}")
+ # 如果激活失败,继续创建新窗口
+
+ # 创建新窗口
+ class_name = self.list_combobox.currentText()
+ group_filter = self.range_combobox.currentText()
+ gender_filter = self.gender_combobox.currentText()
+ group_index = self.range_combobox.currentIndex()
+ gender_index = self.gender_combobox.currentIndex()
+ half_repeat = readme_settings_async("roll_call_settings", "half_repeat")
+
+ window, get_page = create_remaining_list_window(
+ class_name,
+ group_filter,
+ gender_filter,
+ half_repeat,
+ group_index,
+ gender_index,
)
- ResultDisplayUtils.display_results_in_grid(self.result_grid, student_labels)
\ No newline at end of file
+
+ def on_page_ready(page):
+ self.remaining_list_page = page
+
+ if page and hasattr(page, "count_changed"):
+ page.count_changed.connect(self.update_many_count_label)
+ self.update_many_count_label()
+
+ get_page(on_page_ready)
+
+ window.windowClosed.connect(lambda: setattr(self, "remaining_list_page", None))
+
+ window.show()
+
+ def setup_file_watcher(self):
+ """设置文件监控器,监控名单文件夹的变化"""
+ try:
+ list_dir = get_path("app/resources/list/roll_call_list")
+
+ if not list_dir.exists():
+ list_dir.mkdir(parents=True, exist_ok=True)
+
+ self.file_watcher.addPath(str(list_dir))
+
+ self.file_watcher.directoryChanged.connect(self.on_directory_changed)
+ self.file_watcher.fileChanged.connect(self.on_file_changed)
+
+ except Exception as e:
+ logger.error(f"设置文件监控器失败: {e}")
+
+ def on_directory_changed(self, path):
+ """当文件夹内容发生变化时触发"""
+ try:
+ QTimer.singleShot(500, self.refresh_class_list)
+ except Exception as e:
+ logger.error(f"处理文件夹变化事件失败: {e}")
+
+ def on_file_changed(self, path):
+ """当文件内容发生变化时触发"""
+ try:
+ QTimer.singleShot(500, self.refresh_class_list)
+ except Exception as e:
+ logger.error(f"处理文件变化事件失败: {e}")
+
+ def refresh_class_list(self):
+ """刷新班级列表下拉框"""
+ try:
+ current_class = self.list_combobox.currentText()
+
+ new_class_list = get_class_name_list()
+
+ self.list_combobox.blockSignals(True)
+
+ self.list_combobox.clear()
+ self.list_combobox.addItems(new_class_list)
+
+ if current_class in new_class_list:
+ index = self.list_combobox.findText(current_class)
+ if index >= 0:
+ self.list_combobox.setCurrentIndex(index)
+ elif new_class_list:
+ self.list_combobox.setCurrentIndex(0)
+
+ self.list_combobox.blockSignals(False)
+
+ self.on_class_changed()
+
+ except Exception as e:
+ logger.error(f"刷新班级列表失败: {e}")
+
+ def populate_lists(self):
+ """在后台填充班级/范围/性别下拉框并更新人数统计"""
+ try:
+ # 填充班级列表
+ class_list = get_class_name_list()
+ self.list_combobox.blockSignals(True)
+ self.list_combobox.clear()
+ if class_list:
+ self.list_combobox.addItems(class_list)
+ self.list_combobox.setCurrentIndex(0)
+ self.list_combobox.blockSignals(False)
+
+ # 填充范围和性别选项
+ self.range_combobox.blockSignals(True)
+ self.range_combobox.clear()
+
+ # 获取基础选项
+ base_options = get_content_combo_name_async("roll_call", "range_combobox")
+
+ # 获取小组列表
+ group_list = get_group_list(self.list_combobox.currentText())
+
+ # 如果有小组,才添加"抽取全部小组"选项
+ if group_list:
+ # 添加基础选项和小组列表
+ self.range_combobox.addItems(base_options + group_list)
+ else:
+ # 只添加基础选项,跳过"抽取全部小组"
+ self.range_combobox.addItems(base_options[:1]) # 只添加"抽取全部学生"
+
+ self.range_combobox.blockSignals(False)
+
+ self.gender_combobox.blockSignals(True)
+ self.gender_combobox.clear()
+ self.gender_combobox.addItems(
+ get_content_combo_name_async("roll_call", "gender_combobox")
+ + get_gender_list(self.list_combobox.currentText())
+ )
+ self.gender_combobox.blockSignals(False)
+
+ # 根据当前选择的范围计算实际的总人数
+ group_index = self.range_combobox.currentIndex()
+ group_filter = self.range_combobox.currentText()
+ gender_filter = self.gender_combobox.currentText()
+
+ # 根据范围计算实际人数
+ if group_index == 0: # 全班
+ total_count = len(get_student_list(self.list_combobox.currentText()))
+ elif group_index == 1: # 小组模式 - 计算小组数量
+ total_count = len(get_group_list(self.list_combobox.currentText()))
+ else: # 特定小组 - 计算该小组的学生数量
+ students = get_student_list(self.list_combobox.currentText())
+ total_count = len([s for s in students if s["group"] == group_filter])
+
+ self.remaining_count = calculate_remaining_count(
+ half_repeat=readme_settings("roll_call_settings", "half_repeat"),
+ class_name=self.list_combobox.currentText(),
+ gender_filter=gender_filter,
+ group_index=group_index,
+ group_filter=group_filter,
+ total_count=total_count,
+ )
+
+ # 根据是否为小组模式选择不同的文本模板
+ if group_index == 1: # 小组模式
+ text_template = get_any_position_value(
+ "roll_call", "many_count_label", "text_3"
+ )
+ else: # 学生模式
+ text_template = get_any_position_value(
+ "roll_call", "many_count_label", "text_0"
+ )
+ formatted_text = text_template.format(
+ total_count=total_count, remaining_count=self.remaining_count
+ )
+ self.many_count_label.setText(formatted_text)
+
+ # 根据总人数是否为0,启用或禁用开始按钮
+ if total_count == 0:
+ self.start_button.setEnabled(False)
+ else:
+ self.start_button.setEnabled(True)
+
+ except Exception as e:
+ logger.error(f"延迟填充列表失败: {e}")
diff --git a/app/view/main/window.py b/app/view/main/window.py
index 5946b1c8..ccfa9b7a 100644
--- a/app/view/main/window.py
+++ b/app/view/main/window.py
@@ -1,27 +1,25 @@
# ==================================================
# 导入库
# ==================================================
-import json
-import os
import sys
import subprocess
import loguru
from loguru import logger
-from PyQt6.QtWidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
-from qfluentwidgets import *
-
-from app.tools.variable import *
-from app.tools.path_utils import *
-from app.tools.personalised import *
-from app.Language.obtain_language import *
-from app.page_building.main_window_page import *
+from PySide6.QtWidgets import QApplication, QWidget
+from PySide6.QtGui import QIcon
+from PySide6.QtCore import QTimer, QEvent, Signal
+from qfluentwidgets import MSFluentWindow, NavigationItemPosition
+
+from app.tools.variable import MINIMUM_WINDOW_SIZE, APP_INIT_DELAY
+from app.tools.path_utils import get_resources_path
+from app.tools.path_utils import get_app_root
+from app.tools.personalised import get_theme_icon
+from app.Language.obtain_language import get_content_name_async
+from app.Language.obtain_language import readme_settings_async, update_settings
+from app.page_building.main_window_page import roll_call_page
from app.view.tray.tray import Tray
-from app.tools.extract import _is_non_class_time
# ==================================================
# 主窗口类
@@ -29,35 +27,46 @@
class MainWindow(MSFluentWindow):
"""主窗口类
程序的核心控制中心"""
- showSettingsRequested = pyqtSignal()
- showSettingsRequestedAbout = pyqtSignal()
-
+
+ showSettingsRequested = Signal()
+ showSettingsRequestedAbout = Signal()
+
def __init__(self):
super().__init__()
# 设置窗口对象名称,方便其他组件查找
self.setObjectName("MainWindow")
+ self.roll_call_page = None
+ self.settingsInterface = None
+
+ self.roll_call_page = None
+ self.settingsInterface = None
+
# resize_timer的初始化
self.resize_timer = QTimer(self)
self.resize_timer.setSingleShot(True)
- self.resize_timer.timeout.connect(lambda: self.save_window_size(self.width(), self.height()))
+ self.resize_timer.timeout.connect(
+ lambda: self.save_window_size(self.width(), self.height())
+ )
# 设置窗口属性
self.setMinimumSize(MINIMUM_WINDOW_SIZE[0], MINIMUM_WINDOW_SIZE[1])
- self.setWindowTitle('SecRandom')
- self.setWindowIcon(QIcon(str(get_resources_path('assets/icon', 'secrandom-icon-paper.png'))))
+ self.setWindowTitle("SecRandom")
+ self.setWindowIcon(
+ QIcon(str(get_resources_path("assets/icon", "secrandom-icon-paper.png")))
+ )
self._position_window()
# 导入并创建托盘图标
tray_icon = Tray(self)
tray_icon.showSettingsRequested.connect(self.showSettingsRequested.emit)
- tray_icon.showSettingsRequestedAbout.connect(self.showSettingsRequestedAbout.emit)
+ tray_icon.showSettingsRequestedAbout.connect(
+ self.showSettingsRequestedAbout.emit
+ )
tray_icon.show_tray_icon()
-
- QTimer.singleShot(APP_INIT_DELAY, lambda: (
- self.createSubInterface()
- ))
+
+ QTimer.singleShot(APP_INIT_DELAY, lambda: (self.createSubInterface()))
def _position_window(self):
"""窗口定位
@@ -65,7 +74,9 @@ def _position_window(self):
is_maximized = readme_settings_async("window", "is_maximized")
if is_maximized:
pre_maximized_width = readme_settings_async("window", "pre_maximized_width")
- pre_maximized_height = readme_settings_async("window", "pre_maximized_height")
+ pre_maximized_height = readme_settings_async(
+ "window", "pre_maximized_height"
+ )
self.resize(pre_maximized_width, pre_maximized_height)
self._center_window()
QTimer.singleShot(100, self.showMaximized)
@@ -84,7 +95,7 @@ def _center_window(self):
target_x = w // 2 - self.width() // 2
target_y = h // 2 - self.height() // 2
-
+
self.move(target_x, target_y)
def _apply_window_visibility_settings(self):
@@ -110,37 +121,42 @@ def createSubInterface(self):
def initNavigation(self):
"""初始化导航系统
根据用户设置构建个性化菜单导航"""
- self.addSubInterface(self.roll_call_page, get_theme_icon("ic_fluent_people_20_filled"), get_content_name_async("roll_call", "title"), position=NavigationItemPosition.TOP)
+ self.addSubInterface(
+ self.roll_call_page,
+ get_theme_icon("ic_fluent_people_20_filled"),
+ get_content_name_async("roll_call", "title"),
+ position=NavigationItemPosition.TOP,
+ )
- settings_item = self.addSubInterface(self.settingsInterface, get_theme_icon("ic_fluent_settings_20_filled"), '设置', position=NavigationItemPosition.BOTTOM)
- settings_item.clicked.connect(self.showSettingsRequested.emit)
+ settings_item = self.addSubInterface(
+ self.settingsInterface,
+ get_theme_icon("ic_fluent_settings_20_filled"),
+ "设置",
+ position=NavigationItemPosition.BOTTOM,
+ )
+ settings_item.clicked.connect(lambda: self.showSettingsRequested.emit())
+ settings_item.clicked.connect(lambda: self.switchTo(self.roll_call_page))
def closeEvent(self, event):
"""窗口关闭事件处理
拦截窗口关闭事件,隐藏窗口并保存窗口大小"""
self.hide()
event.ignore()
-
+
# 保存当前窗口状态
is_maximized = self.isMaximized()
update_settings("window", "is_maximized", is_maximized)
-
- # 如果是最大化状态,保存当前窗口大小作为最大化前的大小
if is_maximized:
- # 最大化状态下,窗口大小是屏幕大小,不需要保存
- # 使用之前保存的最大化前的大小
pass
else:
- # 非最大化状态,保存当前窗口大小
self.save_window_size(self.width(), self.height())
def resizeEvent(self, event):
"""窗口大小变化事件处理
检测窗口大小变化,但不启动尺寸记录倒计时,减少IO操作"""
- # 正常的窗口大小变化处理
self.resize_timer.start(500)
super().resizeEvent(event)
-
+
def changeEvent(self, event):
"""窗口状态变化事件处理
检测窗口最大化/恢复状态变化,保存正确的窗口大小"""
@@ -148,25 +164,36 @@ def changeEvent(self, event):
if event.type() == QEvent.Type.WindowStateChange:
is_currently_maximized = self.isMaximized()
was_maximized = readme_settings_async("window", "is_maximized")
-
+
# 如果最大化状态发生变化
if is_currently_maximized != was_maximized:
# 更新最大化状态
update_settings("window", "is_maximized", is_currently_maximized)
-
+
# 如果进入最大化,保存当前窗口大小作为最大化前的大小
if is_currently_maximized:
# 获取正常状态下的窗口大小
normal_geometry = self.normalGeometry()
- update_settings("window", "pre_maximized_width", normal_geometry.width())
- update_settings("window", "pre_maximized_height", normal_geometry.height())
+ update_settings(
+ "window", "pre_maximized_width", normal_geometry.width()
+ )
+ update_settings(
+ "window", "pre_maximized_height", normal_geometry.height()
+ )
# 如果退出最大化,恢复到最大化前的大小
else:
- pre_maximized_width = readme_settings_async("window", "pre_maximized_width")
- pre_maximized_height = readme_settings_async("window", "pre_maximized_height")
+ pre_maximized_width = readme_settings_async(
+ "window", "pre_maximized_width"
+ )
+ pre_maximized_height = readme_settings_async(
+ "window", "pre_maximized_height"
+ )
# 延迟执行,确保在最大化状态完全退出后再调整大小
- QTimer.singleShot(100, lambda: self.resize(pre_maximized_width, pre_maximized_height))
-
+ QTimer.singleShot(
+ 100,
+ lambda: self.resize(pre_maximized_width, pre_maximized_height),
+ )
+
super().changeEvent(event)
def save_window_size(self, width, height):
@@ -212,17 +239,18 @@ def restart_app(self):
执行安全验证后重启程序,清理所有资源"""
try:
working_dir = str(get_app_root())
-
- filtered_args = [arg for arg in sys.argv if not arg.startswith('--')]
-
+
+ filtered_args = [arg for arg in sys.argv if not arg.startswith("--")]
+
startup_info = subprocess.STARTUPINFO()
startup_info.dwFlags |= subprocess.STARTF_USESHOWWINDOW
-
+
subprocess.Popen(
[sys.executable] + filtered_args,
cwd=working_dir,
- creationflags=subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.DETACHED_PROCESS,
- startupinfo=startup_info
+ creationflags=subprocess.CREATE_NEW_PROCESS_GROUP
+ | subprocess.DETACHED_PROCESS,
+ startupinfo=startup_info,
)
except Exception as e:
logger.error(f"启动新进程失败: {e}")
@@ -232,7 +260,7 @@ def restart_app(self):
loguru.logger.remove()
except Exception as e:
logger.error(f"日志系统关闭出错: {e}")
-
+
# 完全退出当前应用程序
QApplication.quit()
sys.exit(0)
@@ -240,4 +268,4 @@ def restart_app(self):
def show_about_tab(self):
"""显示关于页面
打开设置窗口并导航到关于页面"""
- self.showSettingsRequestedAbout.emit()
\ No newline at end of file
+ self.showSettingsRequestedAbout.emit()
diff --git a/app/view/settings/__init__.py b/app/view/settings/__init__.py
new file mode 100644
index 00000000..01cba241
--- /dev/null
+++ b/app/view/settings/__init__.py
@@ -0,0 +1 @@
+"""Settings views package."""
diff --git a/app/view/settings/about.py b/app/view/settings/about.py
index 80ea0157..fc979b9b 100644
--- a/app/view/settings/about.py
+++ b/app/view/settings/about.py
@@ -1,16 +1,12 @@
# ==================================================
# 导入库
# ==================================================
-import json
-import os
-import sys
-import subprocess
from loguru import logger
-from PyQt6.QtWidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from PySide6.QtNetwork import *
from qfluentwidgets import *
from qfluentwidgets import FluentIcon as FIF
@@ -21,7 +17,8 @@
from app.tools.settings_access import *
from app.Language.obtain_language import *
-from app.common.another_window import ContributorDialog
+from app.page_building.another_window import create_contributor_window
+
# ==================================================
# 关于卡片类
@@ -33,62 +30,127 @@ def __init__(self, parent=None):
self.setBorderRadius(8)
# 打开GitHub按钮
- self.about_github_Button = HyperlinkButton(FIF.GITHUB, GITHUB_WEB, get_content_name_async("about", "github"))
+ self.about_github_Button = HyperlinkButton(
+ FIF.GITHUB, GITHUB_WEB, get_content_name_async("about", "github")
+ )
# 打开bilibili按钮
- self.about_bilibili_Button = HyperlinkButton(BILIBILI_WEB, get_content_name_async("about", "bilibili"))
+ self.about_bilibili_Button = HyperlinkButton(
+ BILIBILI_WEB, get_content_name_async("about", "bilibili")
+ )
# 打开网站按钮
- self.about_website_Button = HyperlinkButton(WEBSITE, get_content_name_async("about", "website"))
+ self.about_website_Button = HyperlinkButton(
+ WEBSITE, get_content_name_async("about", "website")
+ )
# 查看当前软件版本号
- version_text = f"Dev Version-{NEXT_VERSION}" if VERSION == "v0.0.0.0" else VERSION
+ version_text = (
+ f"Dev Version-{NEXT_VERSION}" if VERSION == "v0.0.0.0" else VERSION
+ )
self.about_version_label = BodyLabel(version_text)
# 查看当前软件版权所属
self.about_author_label = BodyLabel(f"Copyright © {YEAR} {APPLY_NAME}")
# 创建贡献人员按钮
- self.contributor_button = PushButton(get_content_name_async("about", "contributor"))
- self.contributor_button.setIcon(get_theme_icon("ic_fluent_document_person_20_filled"))
+ self.contributor_button = PushButton(
+ get_content_name_async("about", "contributor")
+ )
+ self.contributor_button.setIcon(
+ get_theme_icon("ic_fluent_document_person_20_filled")
+ )
self.contributor_button.clicked.connect(self.show_contributors)
# 创建捐赠支持按钮
self.donation_button = PushButton(get_content_name_async("about", "donation"))
- self.donation_button.setIcon(get_theme_icon("ic_fluent_document_person_20_filled"))
+ self.donation_button.setIcon(
+ get_theme_icon("ic_fluent_document_person_20_filled")
+ )
self.donation_button.clicked.connect(self.open_donation_url)
# 检查更新按钮
- self.check_update_button = PushButton(get_content_name_async("about", "check_update"))
- self.check_update_button.setIcon(get_theme_icon("ic_fluent_arrow_sync_20_filled"))
+ self.check_update_button = PushButton(
+ get_content_name_async("about", "check_update")
+ )
+ self.check_update_button.setIcon(
+ get_theme_icon("ic_fluent_arrow_sync_20_filled")
+ )
self.check_update_button.clicked.connect(self.check_updates)
# 添加更新通道选择
self.channel_combo = ComboBox()
self.channel_combo.addItems(get_content_combo_name_async("about", "channel"))
self.channel_combo.setCurrentIndex(readme_settings_async("about", "channel"))
- self.channel_combo.currentIndexChanged.connect(lambda: update_settings("about", "channel", self.channel_combo.currentIndex()))
-
- self.addGroup(get_theme_icon("ic_fluent_branch_fork_link_20_filled"), get_content_name_async("about", "bilibili"), get_content_description_async("about", "bilibili"), self.about_bilibili_Button)
- self.addGroup(FIF.GITHUB, get_content_name_async("about", "github"), get_content_description_async("about", "github"), self.about_github_Button)
- self.addGroup(get_theme_icon("ic_fluent_document_person_20_filled"), get_content_name_async("about", "contributor"), get_content_description_async("about", "contributor"), self.contributor_button)
- self.addGroup(get_theme_icon("ic_fluent_document_person_20_filled"), get_content_name_async("about", "donation"), get_content_description_async("about", "donation"), self.donation_button)
- self.addGroup(get_theme_icon("ic_fluent_class_20_filled"), get_content_name_async("about", "copyright"), get_content_description_async("about", "copyright"), self.about_author_label)
- self.addGroup(FIF.GLOBE, get_content_name_async("about", "website"), get_content_description_async("about", "website"), self.about_website_Button)
- self.addGroup(get_theme_icon("ic_fluent_info_20_filled"), get_content_name_async("about", "version"), get_content_description_async("about", "version"), self.about_version_label)
- self.addGroup(get_theme_icon("ic_fluent_arrow_sync_20_filled"), get_content_name_async("about", "channel"), get_content_description_async("about", "channel"), self.channel_combo)
- self.addGroup(get_theme_icon("ic_fluent_arrow_sync_20_filled"), get_content_name_async("about", "check_update"), get_content_description_async("about", "check_update"), self.check_update_button)
+ self.channel_combo.currentIndexChanged.connect(
+ lambda: update_settings(
+ "about", "channel", self.channel_combo.currentIndex()
+ )
+ )
+
+ self.addGroup(
+ get_theme_icon("ic_fluent_branch_fork_link_20_filled"),
+ get_content_name_async("about", "bilibili"),
+ get_content_description_async("about", "bilibili"),
+ self.about_bilibili_Button,
+ )
+ self.addGroup(
+ FIF.GITHUB,
+ get_content_name_async("about", "github"),
+ get_content_description_async("about", "github"),
+ self.about_github_Button,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_document_person_20_filled"),
+ get_content_name_async("about", "contributor"),
+ get_content_description_async("about", "contributor"),
+ self.contributor_button,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_document_person_20_filled"),
+ get_content_name_async("about", "donation"),
+ get_content_description_async("about", "donation"),
+ self.donation_button,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_class_20_filled"),
+ get_content_name_async("about", "copyright"),
+ get_content_description_async("about", "copyright"),
+ self.about_author_label,
+ )
+ self.addGroup(
+ FIF.GLOBE,
+ get_content_name_async("about", "website"),
+ get_content_description_async("about", "website"),
+ self.about_website_Button,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_info_20_filled"),
+ get_content_name_async("about", "version"),
+ get_content_description_async("about", "version"),
+ self.about_version_label,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_arrow_sync_20_filled"),
+ get_content_name_async("about", "channel"),
+ get_content_description_async("about", "channel"),
+ self.channel_combo,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_arrow_sync_20_filled"),
+ get_content_name_async("about", "check_update"),
+ get_content_description_async("about", "check_update"),
+ self.check_update_button,
+ )
def show_contributors(self):
- """ 显示贡献人员 """
- w = ContributorDialog(self)
- if w.exec():
- pass
+ """显示贡献人员"""
+ create_contributor_window()
def open_donation_url(self):
- """ 打开捐赠链接 """
+ """打开捐赠链接"""
QDesktopServices.openUrl(QUrl(DONATION_URL))
def check_updates(self):
- """ 检查更新 """
- logger.debug("检查更新")
\ No newline at end of file
+ """检查更新"""
+ logger.debug("检查更新")
diff --git a/app/view/settings/basic_settings.py b/app/view/settings/basic_settings.py
index 98c7f961..90e2f051 100644
--- a/app/view/settings/basic_settings.py
+++ b/app/view/settings/basic_settings.py
@@ -1,24 +1,32 @@
# ==================================================
# 导入库
# ==================================================
-import json
-import os
-import sys
-import subprocess
-
-from loguru import logger
-from PyQt6.QtWidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
-from qfluentwidgets import *
-
-from app.tools.variable import *
-from app.tools.path_utils import *
-from app.tools.personalised import *
-from app.tools.settings_default import *
-from app.tools.settings_access import *
-from app.Language.obtain_language import *
+
+from PySide6.QtWidgets import QWidget, QVBoxLayout
+from PySide6.QtGui import QFontDatabase
+from qfluentwidgets import (
+ GroupHeaderCardWidget,
+ SwitchButton,
+ ComboBox,
+ PushButton,
+ ColorConfigItem,
+ ColorSettingCard,
+ Theme,
+ setTheme,
+ setThemeColor,
+)
+
+from app.tools.personalised import get_theme_icon
+from app.tools.settings_access import readme_settings_async, update_settings
+from app.Language.obtain_language import (
+ get_all_languages_name,
+ get_content_combo_name_async,
+ get_content_description_async,
+ get_content_name_async,
+ get_content_pushbutton_name_async,
+ get_content_switchbutton_name_async,
+)
+
# ==================================================
# 基本设置
@@ -43,6 +51,7 @@ def __init__(self, parent=None):
self.data_management_widget = basic_settings_data_management(self)
self.vBoxLayout.addWidget(self.data_management_widget)
+
class basic_settings_function(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
@@ -51,32 +60,87 @@ def __init__(self, parent=None):
# 开机自启设置
self.autostart_switch = SwitchButton()
- self.autostart_switch.setOffText(get_content_switchbutton_name_async("basic_settings", "autostart", "disable"))
- self.autostart_switch.setOnText(get_content_switchbutton_name_async("basic_settings", "autostart", "enable"))
- self.autostart_switch.setChecked(readme_settings_async("basic_settings", "autostart"))
- self.autostart_switch.checkedChanged.connect(lambda: update_settings("basic_settings", "autostart", self.autostart_switch.isChecked()))
+ self.autostart_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "basic_settings", "autostart", "disable"
+ )
+ )
+ self.autostart_switch.setOnText(
+ get_content_switchbutton_name_async("basic_settings", "autostart", "enable")
+ )
+ self.autostart_switch.setChecked(
+ readme_settings_async("basic_settings", "autostart")
+ )
+ self.autostart_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "basic_settings", "autostart", self.autostart_switch.isChecked()
+ )
+ )
# 自动检查更新设置
self.check_update_switch = SwitchButton()
- self.check_update_switch.setOffText(get_content_switchbutton_name_async("basic_settings", "check_update", "disable"))
- self.check_update_switch.setOnText(get_content_switchbutton_name_async("basic_settings", "check_update", "enable"))
- self.check_update_switch.setChecked(readme_settings_async("basic_settings", "check_update"))
- self.check_update_switch.checkedChanged.connect(lambda: update_settings("basic_settings", "check_update", self.check_update_switch.isChecked()))
+ self.check_update_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "basic_settings", "check_update", "disable"
+ )
+ )
+ self.check_update_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "basic_settings", "check_update", "enable"
+ )
+ )
+ self.check_update_switch.setChecked(
+ readme_settings_async("basic_settings", "check_update")
+ )
+ self.check_update_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "basic_settings", "check_update", self.check_update_switch.isChecked()
+ )
+ )
# 显示启动窗口设置
self.show_startup_window_switch = SwitchButton()
- self.show_startup_window_switch.setOffText(get_content_switchbutton_name_async("basic_settings", "show_startup_window", "disable"))
- self.show_startup_window_switch.setOnText(get_content_switchbutton_name_async("basic_settings", "show_startup_window", "enable"))
- self.show_startup_window_switch.setChecked(readme_settings_async("basic_settings", "show_startup_window"))
- self.show_startup_window_switch.checkedChanged.connect(lambda: update_settings("basic_settings", "show_startup_window", self.show_startup_window_switch.isChecked()))
+ self.show_startup_window_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "basic_settings", "show_startup_window", "disable"
+ )
+ )
+ self.show_startup_window_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "basic_settings", "show_startup_window", "enable"
+ )
+ )
+ self.show_startup_window_switch.setChecked(
+ readme_settings_async("basic_settings", "show_startup_window")
+ )
+ self.show_startup_window_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "basic_settings",
+ "show_startup_window",
+ self.show_startup_window_switch.isChecked(),
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_arrow_sync_20_filled"),
- get_content_name_async("basic_settings", "autostart"), get_content_description_async("basic_settings", "autostart"), self.autostart_switch)
- self.addGroup(get_theme_icon("ic_fluent_cloud_sync_20_filled"),
- get_content_name_async("basic_settings", "check_update"), get_content_description_async("basic_settings", "check_update"), self.check_update_switch)
- self.addGroup(get_theme_icon("ic_fluent_window_play_20_filled"),
- get_content_name_async("basic_settings", "show_startup_window"), get_content_description_async("basic_settings", "show_startup_window"), self.show_startup_window_switch)
+ self.addGroup(
+ get_theme_icon("ic_fluent_arrow_sync_20_filled"),
+ get_content_name_async("basic_settings", "autostart"),
+ get_content_description_async("basic_settings", "autostart"),
+ self.autostart_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_cloud_sync_20_filled"),
+ get_content_name_async("basic_settings", "check_update"),
+ get_content_description_async("basic_settings", "check_update"),
+ self.check_update_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_window_play_20_filled"),
+ get_content_name_async("basic_settings", "show_startup_window"),
+ get_content_description_async("basic_settings", "show_startup_window"),
+ self.show_startup_window_switch,
+ )
+
class basic_settings_personalised(GroupHeaderCardWidget):
def __init__(self, parent=None):
@@ -91,31 +155,59 @@ def __init__(self, parent=None):
current_theme = readme_settings_async("basic_settings", "theme")
self.theme.setCurrentIndex(theme_to_index.get(current_theme, 2))
index_to_theme = {0: Theme.LIGHT, 1: Theme.DARK, 2: Theme.AUTO}
- self.theme.currentIndexChanged.connect(lambda index: update_settings("basic_settings", "theme", ["LIGHT", "DARK", "AUTO"][index]))
- self.theme.currentIndexChanged.connect(lambda index: setTheme(index_to_theme.get(index)))
+ self.theme.currentIndexChanged.connect(
+ lambda index: update_settings(
+ "basic_settings", "theme", ["LIGHT", "DARK", "AUTO"][index]
+ )
+ )
+ self.theme.currentIndexChanged.connect(
+ lambda index: setTheme(index_to_theme.get(index))
+ )
# 主题色设置卡片
- self.themeColor = ColorConfigItem("basic_settings", "theme_color", readme_settings_async("basic_settings", "theme_color"))
- self.themeColor.valueChanged.connect(lambda color: update_settings("basic_settings", "theme_color", color.name()))
+ self.themeColor = ColorConfigItem(
+ "basic_settings",
+ "theme_color",
+ readme_settings_async("basic_settings", "theme_color"),
+ )
+ self.themeColor.valueChanged.connect(
+ lambda color: update_settings("basic_settings", "theme_color", color.name())
+ )
self.themeColor.valueChanged.connect(lambda color: setThemeColor(color))
# 语言设置卡片
self.language = ComboBox()
self.language.addItems(get_all_languages_name())
- self.language.setCurrentText(readme_settings_async("basic_settings", "language"))
- self.language.currentTextChanged.connect(lambda language: update_settings("basic_settings", "language", language))
+ self.language.setCurrentText(
+ readme_settings_async("basic_settings", "language")
+ )
+ self.language.currentTextChanged.connect(
+ lambda language: update_settings("basic_settings", "language", language)
+ )
# 字体设置卡片
self.fontComboBox = ComboBox()
- self.fontComboBox.addItems(["HarmonyOS Sans SC"] + sorted(QFontDatabase.families()))
- self.fontComboBox.setCurrentText(readme_settings_async("basic_settings", "font"))
- self.fontComboBox.currentTextChanged.connect(lambda font: update_settings("basic_settings", "font", font))
+ self.fontComboBox.addItems(
+ ["HarmonyOS Sans SC"] + sorted(QFontDatabase.families())
+ )
+ self.fontComboBox.setCurrentText(
+ readme_settings_async("basic_settings", "font")
+ )
+ self.fontComboBox.currentTextChanged.connect(
+ lambda font: update_settings("basic_settings", "font", font)
+ )
# 界面缩放设置卡片
self.dpiScale = ComboBox()
- self.dpiScale.addItems(get_content_combo_name_async("basic_settings", "dpiScale"))
- self.dpiScale.setCurrentText(readme_settings_async("basic_settings", "dpiScale"))
- self.dpiScale.currentTextChanged.connect(lambda scale: update_settings("basic_settings", "dpiScale", scale))
+ self.dpiScale.addItems(
+ get_content_combo_name_async("basic_settings", "dpiScale")
+ )
+ self.dpiScale.setCurrentText(
+ readme_settings_async("basic_settings", "dpiScale")
+ )
+ self.dpiScale.currentTextChanged.connect(
+ lambda scale: update_settings("basic_settings", "dpiScale", scale)
+ )
# 主题色设置卡片
self.themeColorCard = ColorSettingCard(
@@ -126,17 +218,34 @@ def __init__(self, parent=None):
)
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_dark_theme_20_filled"),
- get_content_name_async("basic_settings", "theme"), get_content_description_async("basic_settings", "theme"), self.theme)
- self.addGroup(get_theme_icon("ic_fluent_local_language_20_filled"),
- get_content_name_async("basic_settings", "language"), get_content_description_async("basic_settings", "language"), self.language)
- self.addGroup(get_theme_icon("ic_fluent_text_font_20_filled"),
- get_content_name_async("basic_settings", "font"), get_content_description_async("basic_settings", "font"), self.fontComboBox)
- self.addGroup(get_theme_icon("ic_fluent_zoom_fit_20_filled"),
- get_content_name_async("basic_settings", "dpiScale"), get_content_description_async("basic_settings", "dpiScale"), self.dpiScale)
+ self.addGroup(
+ get_theme_icon("ic_fluent_dark_theme_20_filled"),
+ get_content_name_async("basic_settings", "theme"),
+ get_content_description_async("basic_settings", "theme"),
+ self.theme,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_local_language_20_filled"),
+ get_content_name_async("basic_settings", "language"),
+ get_content_description_async("basic_settings", "language"),
+ self.language,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_text_font_20_filled"),
+ get_content_name_async("basic_settings", "font"),
+ get_content_description_async("basic_settings", "font"),
+ self.fontComboBox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_zoom_fit_20_filled"),
+ get_content_name_async("basic_settings", "dpiScale"),
+ get_content_description_async("basic_settings", "dpiScale"),
+ self.dpiScale,
+ )
# 添加卡片到布局
self.vBoxLayout.addWidget(self.themeColorCard)
+
class basic_settings_data_management(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
@@ -144,33 +253,67 @@ def __init__(self, parent=None):
self.setBorderRadius(8)
# 导出诊断数据按钮
- self.export_diagnostic_data_button = PushButton(get_content_pushbutton_name_async("basic_settings", "export_diagnostic_data"))
- self.export_diagnostic_data_button.clicked.connect(lambda: self.export_diagnostic_data())
+ self.export_diagnostic_data_button = PushButton(
+ get_content_pushbutton_name_async(
+ "basic_settings", "export_diagnostic_data"
+ )
+ )
+ self.export_diagnostic_data_button.clicked.connect(
+ lambda: self.export_diagnostic_data()
+ )
# 导出设置按钮
- self.export_settings_button = PushButton(get_content_pushbutton_name_async("basic_settings", "export_settings"))
+ self.export_settings_button = PushButton(
+ get_content_pushbutton_name_async("basic_settings", "export_settings")
+ )
self.export_settings_button.clicked.connect(lambda: self.export_settings())
# 导入设置按钮
- self.import_settings_button = PushButton(get_content_pushbutton_name_async("basic_settings", "import_settings"))
+ self.import_settings_button = PushButton(
+ get_content_pushbutton_name_async("basic_settings", "import_settings")
+ )
self.import_settings_button.clicked.connect(lambda: self.import_settings())
# 导出软件所有数据按钮
- self.export_all_data_button = PushButton(get_content_pushbutton_name_async("basic_settings", "export_all_data"))
+ self.export_all_data_button = PushButton(
+ get_content_pushbutton_name_async("basic_settings", "export_all_data")
+ )
self.export_all_data_button.clicked.connect(lambda: self.export_all_data())
# 导入软件所有数据按钮
- self.import_all_data_button = PushButton(get_content_pushbutton_name_async("basic_settings", "import_all_data"))
+ self.import_all_data_button = PushButton(
+ get_content_pushbutton_name_async("basic_settings", "import_all_data")
+ )
self.import_all_data_button.clicked.connect(lambda: self.import_all_data())
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_database_arrow_down_20_filled"),
- get_content_name_async("basic_settings", "export_diagnostic_data"), get_content_description_async("basic_settings", "export_diagnostic_data"), self.export_diagnostic_data_button)
- self.addGroup(get_theme_icon("ic_fluent_arrow_clockwise_dashes_settings_20_filled"),
- get_content_name_async("basic_settings", "export_settings"), get_content_description_async("basic_settings", "export_settings"), self.export_settings_button)
- self.addGroup(get_theme_icon("ic_fluent_arrow_clockwise_dashes_settings_20_filled"),
- get_content_name_async("basic_settings", "import_settings"), get_content_description_async("basic_settings", "import_settings"), self.import_settings_button)
- self.addGroup(get_theme_icon("ic_fluent_database_window_20_filled"),
- get_content_name_async("basic_settings", "export_all_data"), get_content_description_async("basic_settings", "export_all_data"), self.export_all_data_button)
- self.addGroup(get_theme_icon("ic_fluent_database_window_20_filled"),
- get_content_name_async("basic_settings", "import_all_data"), get_content_description_async("basic_settings", "import_all_data"), self.import_all_data_button)
\ No newline at end of file
+ self.addGroup(
+ get_theme_icon("ic_fluent_database_arrow_down_20_filled"),
+ get_content_name_async("basic_settings", "export_diagnostic_data"),
+ get_content_description_async("basic_settings", "export_diagnostic_data"),
+ self.export_diagnostic_data_button,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_arrow_clockwise_dashes_settings_20_filled"),
+ get_content_name_async("basic_settings", "export_settings"),
+ get_content_description_async("basic_settings", "export_settings"),
+ self.export_settings_button,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_arrow_clockwise_dashes_settings_20_filled"),
+ get_content_name_async("basic_settings", "import_settings"),
+ get_content_description_async("basic_settings", "import_settings"),
+ self.import_settings_button,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_database_window_20_filled"),
+ get_content_name_async("basic_settings", "export_all_data"),
+ get_content_description_async("basic_settings", "export_all_data"),
+ self.export_all_data_button,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_database_window_20_filled"),
+ get_content_name_async("basic_settings", "import_all_data"),
+ get_content_description_async("basic_settings", "import_all_data"),
+ self.import_all_data_button,
+ )
diff --git a/app/view/settings/custom_settings/__init__.py b/app/view/settings/custom_settings/__init__.py
new file mode 100644
index 00000000..14b8327f
--- /dev/null
+++ b/app/view/settings/custom_settings/__init__.py
@@ -0,0 +1 @@
+"""Custom settings pages."""
diff --git a/app/view/settings/custom_settings/floating_window_management.py b/app/view/settings/custom_settings/floating_window_management.py
index 269c9818..28d19a05 100644
--- a/app/view/settings/custom_settings/floating_window_management.py
+++ b/app/view/settings/custom_settings/floating_window_management.py
@@ -1,16 +1,12 @@
# ==================================================
# 导入库
# ==================================================
-import json
-import os
-import sys
-import subprocess
from loguru import logger
-from PyQt6.QtWidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from PySide6.QtNetwork import *
from qfluentwidgets import *
from app.tools.variable import *
@@ -20,6 +16,7 @@
from app.tools.settings_access import *
from app.Language.obtain_language import *
+
# ==================================================
# 浮动窗口管理 - 主容器
# ==================================================
@@ -43,64 +40,119 @@ def __init__(self, parent=None):
self.edge_settings = floating_window_edge_settings(self)
self.vBoxLayout.addWidget(self.edge_settings)
+
# ==================================================
# 浮动窗口管理 - 基础设置
# ==================================================
class floating_window_basic_settings(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("floating_window_management", "basic_settings"))
+ self.setTitle(
+ get_content_name_async("floating_window_management", "basic_settings")
+ )
self.setBorderRadius(8)
# 软件启动时浮窗显示隐藏开关
self.startup_display_floating_window_switch = SwitchButton()
- self.startup_display_floating_window_switch.setOffText(get_content_switchbutton_name_async("floating_window_management", "startup_display_floating_window", "disable"))
- self.startup_display_floating_window_switch.setOnText(get_content_switchbutton_name_async("floating_window_management", "startup_display_floating_window", "enable"))
- self.startup_display_floating_window_switch.setChecked(readme_settings_async("floating_window_management", "startup_display_floating_window"))
- self.startup_display_floating_window_switch.checkedChanged.connect(self.startup_display_floating_window_switch_changed)
+ self.startup_display_floating_window_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "floating_window_management",
+ "startup_display_floating_window",
+ "disable",
+ )
+ )
+ self.startup_display_floating_window_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "floating_window_management",
+ "startup_display_floating_window",
+ "enable",
+ )
+ )
+ self.startup_display_floating_window_switch.setChecked(
+ readme_settings_async(
+ "floating_window_management", "startup_display_floating_window"
+ )
+ )
+ self.startup_display_floating_window_switch.checkedChanged.connect(
+ self.startup_display_floating_window_switch_changed
+ )
# 浮窗透明度
self.floating_window_opacity_spinbox = SpinBox()
self.floating_window_opacity_spinbox.setFixedWidth(WIDTH_SPINBOX)
self.floating_window_opacity_spinbox.setRange(0, 100)
self.floating_window_opacity_spinbox.setSuffix("%")
- self.floating_window_opacity_spinbox.setValue(readme_settings_async("floating_window_management", "floating_window_opacity") * 100)
- self.floating_window_opacity_spinbox.valueChanged.connect(self.floating_window_opacity_spinbox_changed)
+ self.floating_window_opacity_spinbox.setValue(
+ readme_settings_async(
+ "floating_window_management", "floating_window_opacity"
+ )
+ * 100
+ )
+ self.floating_window_opacity_spinbox.valueChanged.connect(
+ self.floating_window_opacity_spinbox_changed
+ )
# 重置浮窗位置按钮
- self.reset_floating_window_position_button = PushButton(get_content_pushbutton_name_async("floating_window_management", "reset_floating_window_position_button"))
- self.reset_floating_window_position_button.setText(get_content_name_async("floating_window_management", "reset_floating_window_position_button"))
- self.reset_floating_window_position_button.clicked.connect(self.reset_floating_window_position_button_clicked)
+ self.reset_floating_window_position_button = PushButton(
+ get_content_pushbutton_name_async(
+ "floating_window_management", "reset_floating_window_position_button"
+ )
+ )
+ self.reset_floating_window_position_button.setText(
+ get_content_name_async(
+ "floating_window_management", "reset_floating_window_position_button"
+ )
+ )
+ self.reset_floating_window_position_button.clicked.connect(
+ self.reset_floating_window_position_button_clicked
+ )
# 添加设置项到分组
self.addGroup(
- get_theme_icon("ic_fluent_desktop_sync_20_filled"),
- get_content_name_async("floating_window_management", "startup_display_floating_window"),
- get_content_description_async("floating_window_management", "startup_display_floating_window"),
- self.startup_display_floating_window_switch
+ get_theme_icon("ic_fluent_desktop_sync_20_filled"),
+ get_content_name_async(
+ "floating_window_management", "startup_display_floating_window"
+ ),
+ get_content_description_async(
+ "floating_window_management", "startup_display_floating_window"
+ ),
+ self.startup_display_floating_window_switch,
)
self.addGroup(
- get_theme_icon("ic_fluent_brightness_high_20_filled"),
- get_content_name_async("floating_window_management", "floating_window_opacity"),
- get_content_description_async("floating_window_management", "floating_window_opacity"),
- self.floating_window_opacity_spinbox
+ get_theme_icon("ic_fluent_brightness_high_20_filled"),
+ get_content_name_async(
+ "floating_window_management", "floating_window_opacity"
+ ),
+ get_content_description_async(
+ "floating_window_management", "floating_window_opacity"
+ ),
+ self.floating_window_opacity_spinbox,
)
self.addGroup(
- get_theme_icon("ic_fluent_arrow_reset_20_filled"),
- get_content_name_async("floating_window_management", "reset_floating_window_position_button"),
- get_content_description_async("floating_window_management", "reset_floating_window_position_button"),
- self.reset_floating_window_position_button
+ get_theme_icon("ic_fluent_arrow_reset_20_filled"),
+ get_content_name_async(
+ "floating_window_management", "reset_floating_window_position_button"
+ ),
+ get_content_description_async(
+ "floating_window_management", "reset_floating_window_position_button"
+ ),
+ self.reset_floating_window_position_button,
)
def startup_display_floating_window_switch_changed(self, checked):
- update_settings("floating_window_management", "startup_display_floating_window", checked)
+ update_settings(
+ "floating_window_management", "startup_display_floating_window", checked
+ )
def floating_window_opacity_spinbox_changed(self, value):
- update_settings("floating_window_management", "floating_window_opacity", value / 100)
+ update_settings(
+ "floating_window_management", "floating_window_opacity", value / 100
+ )
def reset_floating_window_position_button_clicked(self):
# 这里应该实现重置浮窗位置的逻辑
- logger.info("重置浮窗位置按钮被点击")
+ logger.debug("重置浮窗位置按钮被点击")
+
# ==================================================
# 浮动窗口管理 - 外观设置
@@ -108,55 +160,106 @@ def reset_floating_window_position_button_clicked(self):
class floating_window_appearance_settings(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("floating_window_management", "appearance_settings"))
+ self.setTitle(
+ get_content_name_async("floating_window_management", "appearance_settings")
+ )
self.setBorderRadius(8)
# 浮窗按钮控件配置下拉框
self.floating_window_button_control_combo_box = ComboBox()
- self.floating_window_button_control_combo_box.addItems(get_content_combo_name_async("floating_window_management", "floating_window_button_control"))
- self.floating_window_button_control_combo_box.setCurrentIndex(readme_settings_async("floating_window_management", "floating_window_button_control"))
- self.floating_window_button_control_combo_box.currentIndexChanged.connect(self.floating_window_button_control_combo_box_changed)
+ self.floating_window_button_control_combo_box.addItems(
+ get_content_combo_name_async(
+ "floating_window_management", "floating_window_button_control"
+ )
+ )
+ self.floating_window_button_control_combo_box.setCurrentIndex(
+ readme_settings_async(
+ "floating_window_management", "floating_window_button_control"
+ )
+ )
+ self.floating_window_button_control_combo_box.currentIndexChanged.connect(
+ self.floating_window_button_control_combo_box_changed
+ )
# 浮窗排列方式下拉框
self.floating_window_placement_combo_box = ComboBox()
- self.floating_window_placement_combo_box.addItems(get_content_combo_name_async("floating_window_management", "floating_window_placement"))
- self.floating_window_placement_combo_box.setCurrentIndex(readme_settings_async("floating_window_management", "floating_window_placement"))
- self.floating_window_placement_combo_box.currentIndexChanged.connect(self.floating_window_placement_combo_box_changed)
-
+ self.floating_window_placement_combo_box.addItems(
+ get_content_combo_name_async(
+ "floating_window_management", "floating_window_placement"
+ )
+ )
+ self.floating_window_placement_combo_box.setCurrentIndex(
+ readme_settings_async(
+ "floating_window_management", "floating_window_placement"
+ )
+ )
+ self.floating_window_placement_combo_box.currentIndexChanged.connect(
+ self.floating_window_placement_combo_box_changed
+ )
+
# 浮窗显示样式下拉框
self.floating_window_display_style_combo_box = ComboBox()
- self.floating_window_display_style_combo_box.addItems(get_content_combo_name_async("floating_window_management", "floating_window_display_style"))
- self.floating_window_display_style_combo_box.setCurrentIndex(readme_settings_async("floating_window_management", "floating_window_display_style"))
- self.floating_window_display_style_combo_box.currentIndexChanged.connect(self.floating_window_display_style_combo_box_changed)
+ self.floating_window_display_style_combo_box.addItems(
+ get_content_combo_name_async(
+ "floating_window_management", "floating_window_display_style"
+ )
+ )
+ self.floating_window_display_style_combo_box.setCurrentIndex(
+ readme_settings_async(
+ "floating_window_management", "floating_window_display_style"
+ )
+ )
+ self.floating_window_display_style_combo_box.currentIndexChanged.connect(
+ self.floating_window_display_style_combo_box_changed
+ )
# 添加设置项到分组
self.addGroup(
- get_theme_icon("ic_fluent_button_20_filled"),
- get_content_name_async("floating_window_management", "floating_window_button_control"),
- get_content_description_async("floating_window_management", "floating_window_button_control"),
- self.floating_window_button_control_combo_box
+ get_theme_icon("ic_fluent_button_20_filled"),
+ get_content_name_async(
+ "floating_window_management", "floating_window_button_control"
+ ),
+ get_content_description_async(
+ "floating_window_management", "floating_window_button_control"
+ ),
+ self.floating_window_button_control_combo_box,
)
self.addGroup(
- get_theme_icon("ic_fluent_align_left_20_filled"),
- get_content_name_async("floating_window_management", "floating_window_placement"),
- get_content_description_async("floating_window_management", "floating_window_placement"),
- self.floating_window_placement_combo_box
+ get_theme_icon("ic_fluent_align_left_20_filled"),
+ get_content_name_async(
+ "floating_window_management", "floating_window_placement"
+ ),
+ get_content_description_async(
+ "floating_window_management", "floating_window_placement"
+ ),
+ self.floating_window_placement_combo_box,
)
self.addGroup(
- get_theme_icon("ic_fluent_design_ideas_20_filled"),
- get_content_name_async("floating_window_management", "floating_window_display_style"),
- get_content_description_async("floating_window_management", "floating_window_display_style"),
- self.floating_window_display_style_combo_box
+ get_theme_icon("ic_fluent_design_ideas_20_filled"),
+ get_content_name_async(
+ "floating_window_management", "floating_window_display_style"
+ ),
+ get_content_description_async(
+ "floating_window_management", "floating_window_display_style"
+ ),
+ self.floating_window_display_style_combo_box,
)
def floating_window_button_control_combo_box_changed(self, index):
- update_settings("floating_window_management", "floating_window_button_control", index)
+ update_settings(
+ "floating_window_management", "floating_window_button_control", index
+ )
def floating_window_placement_combo_box_changed(self, index):
- update_settings("floating_window_management", "floating_window_placement", index)
+ update_settings(
+ "floating_window_management", "floating_window_placement", index
+ )
def floating_window_display_style_combo_box_changed(self, index):
- update_settings("floating_window_management", "floating_window_display_style", index)
+ update_settings(
+ "floating_window_management", "floating_window_display_style", index
+ )
+
# ==================================================
# 浮动窗口管理 - 贴边设置
@@ -164,55 +267,118 @@ def floating_window_display_style_combo_box_changed(self, index):
class floating_window_edge_settings(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("floating_window_management", "edge_settings"))
+ self.setTitle(
+ get_content_name_async("floating_window_management", "edge_settings")
+ )
self.setBorderRadius(8)
# 浮窗贴边开关
self.floating_window_stick_to_edge_switch = SwitchButton()
- self.floating_window_stick_to_edge_switch.setOffText(get_content_switchbutton_name_async("floating_window_management", "floating_window_stick_to_edge", "disable"))
- self.floating_window_stick_to_edge_switch.setOnText(get_content_switchbutton_name_async("floating_window_management", "floating_window_stick_to_edge", "enable"))
- self.floating_window_stick_to_edge_switch.setChecked(readme_settings_async("floating_window_management", "floating_window_stick_to_edge"))
- self.floating_window_stick_to_edge_switch.checkedChanged.connect(self.floating_window_stick_to_edge_switch_changed)
+ self.floating_window_stick_to_edge_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "floating_window_management", "floating_window_stick_to_edge", "disable"
+ )
+ )
+ self.floating_window_stick_to_edge_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "floating_window_management", "floating_window_stick_to_edge", "enable"
+ )
+ )
+ self.floating_window_stick_to_edge_switch.setChecked(
+ readme_settings_async(
+ "floating_window_management", "floating_window_stick_to_edge"
+ )
+ )
+ self.floating_window_stick_to_edge_switch.checkedChanged.connect(
+ self.floating_window_stick_to_edge_switch_changed
+ )
# 浮窗贴边回收秒数
self.floating_window_stick_to_edge_recover_seconds_spinbox = SpinBox()
- self.floating_window_stick_to_edge_recover_seconds_spinbox.setFixedWidth(WIDTH_SPINBOX)
+ self.floating_window_stick_to_edge_recover_seconds_spinbox.setFixedWidth(
+ WIDTH_SPINBOX
+ )
self.floating_window_stick_to_edge_recover_seconds_spinbox.setRange(0, 25600)
self.floating_window_stick_to_edge_recover_seconds_spinbox.setSuffix("秒")
- self.floating_window_stick_to_edge_recover_seconds_spinbox.setValue(readme_settings_async("floating_window_management", "floating_window_stick_to_edge_recover_seconds"))
- self.floating_window_stick_to_edge_recover_seconds_spinbox.valueChanged.connect(self.floating_window_stick_to_edge_recover_seconds_spinbox_changed)
+ self.floating_window_stick_to_edge_recover_seconds_spinbox.setValue(
+ readme_settings_async(
+ "floating_window_management",
+ "floating_window_stick_to_edge_recover_seconds",
+ )
+ )
+ self.floating_window_stick_to_edge_recover_seconds_spinbox.valueChanged.connect(
+ self.floating_window_stick_to_edge_recover_seconds_spinbox_changed
+ )
# 浮窗贴边显示样式下拉框
self.floating_window_stick_to_edge_display_style_combo_box = ComboBox()
- self.floating_window_stick_to_edge_display_style_combo_box.addItems(get_content_combo_name_async("floating_window_management", "floating_window_stick_to_edge_display_style"))
- self.floating_window_stick_to_edge_display_style_combo_box.setCurrentIndex(readme_settings_async("floating_window_management", "floating_window_stick_to_edge_display_style"))
- self.floating_window_stick_to_edge_display_style_combo_box.currentIndexChanged.connect(self.floating_window_stick_to_edge_display_style_combo_box_changed)
+ self.floating_window_stick_to_edge_display_style_combo_box.addItems(
+ get_content_combo_name_async(
+ "floating_window_management",
+ "floating_window_stick_to_edge_display_style",
+ )
+ )
+ self.floating_window_stick_to_edge_display_style_combo_box.setCurrentIndex(
+ readme_settings_async(
+ "floating_window_management",
+ "floating_window_stick_to_edge_display_style",
+ )
+ )
+ self.floating_window_stick_to_edge_display_style_combo_box.currentIndexChanged.connect(
+ self.floating_window_stick_to_edge_display_style_combo_box_changed
+ )
# 添加设置项到分组
self.addGroup(
- get_theme_icon("ic_fluent_pin_20_filled"),
- get_content_name_async("floating_window_management", "floating_window_stick_to_edge"),
- get_content_description_async("floating_window_management", "floating_window_stick_to_edge"),
- self.floating_window_stick_to_edge_switch
+ get_theme_icon("ic_fluent_pin_20_filled"),
+ get_content_name_async(
+ "floating_window_management", "floating_window_stick_to_edge"
+ ),
+ get_content_description_async(
+ "floating_window_management", "floating_window_stick_to_edge"
+ ),
+ self.floating_window_stick_to_edge_switch,
)
self.addGroup(
- get_theme_icon("ic_fluent_timer_20_filled"),
- get_content_name_async("floating_window_management", "floating_window_stick_to_edge_recover_seconds"),
- get_content_description_async("floating_window_management", "floating_window_stick_to_edge_recover_seconds"),
- self.floating_window_stick_to_edge_recover_seconds_spinbox
+ get_theme_icon("ic_fluent_timer_20_filled"),
+ get_content_name_async(
+ "floating_window_management",
+ "floating_window_stick_to_edge_recover_seconds",
+ ),
+ get_content_description_async(
+ "floating_window_management",
+ "floating_window_stick_to_edge_recover_seconds",
+ ),
+ self.floating_window_stick_to_edge_recover_seconds_spinbox,
)
self.addGroup(
- get_theme_icon("ic_fluent_desktop_sync_20_filled"),
- get_content_name_async("floating_window_management", "floating_window_stick_to_edge_display_style"),
- get_content_description_async("floating_window_management", "floating_window_stick_to_edge_display_style"),
- self.floating_window_stick_to_edge_display_style_combo_box
+ get_theme_icon("ic_fluent_desktop_sync_20_filled"),
+ get_content_name_async(
+ "floating_window_management",
+ "floating_window_stick_to_edge_display_style",
+ ),
+ get_content_description_async(
+ "floating_window_management",
+ "floating_window_stick_to_edge_display_style",
+ ),
+ self.floating_window_stick_to_edge_display_style_combo_box,
)
def floating_window_stick_to_edge_switch_changed(self, checked):
- update_settings("floating_window_management", "floating_window_stick_to_edge", checked)
+ update_settings(
+ "floating_window_management", "floating_window_stick_to_edge", checked
+ )
def floating_window_stick_to_edge_recover_seconds_spinbox_changed(self, value):
- update_settings("floating_window_management", "floating_window_stick_to_edge_recover_seconds", value)
+ update_settings(
+ "floating_window_management",
+ "floating_window_stick_to_edge_recover_seconds",
+ value,
+ )
def floating_window_stick_to_edge_display_style_combo_box_changed(self, index):
- update_settings("floating_window_management", "floating_window_stick_to_edge_display_style", index)
+ update_settings(
+ "floating_window_management",
+ "floating_window_stick_to_edge_display_style",
+ index,
+ )
diff --git a/app/view/settings/custom_settings/page_management.py b/app/view/settings/custom_settings/page_management.py
index a4d5f858..395c1298 100644
--- a/app/view/settings/custom_settings/page_management.py
+++ b/app/view/settings/custom_settings/page_management.py
@@ -1,16 +1,11 @@
# ==================================================
# 导入库
# ==================================================
-import json
-import os
-import sys
-import subprocess
-from loguru import logger
-from PyQt6.QtWidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from PySide6.QtNetwork import *
from qfluentwidgets import *
from app.tools.variable import *
@@ -19,6 +14,9 @@
from app.tools.settings_default import *
from app.tools.settings_access import *
from app.Language.obtain_language import *
+from loguru import logger
+import time
+
# ==================================================
# 页面管理
@@ -31,17 +29,68 @@ def __init__(self, parent=None):
self.vBoxLayout.setContentsMargins(0, 0, 0, 0)
self.vBoxLayout.setSpacing(10)
- # 添加点名页面管理组件
- self.page_management_roll_call = page_management_roll_call(self)
- self.vBoxLayout.addWidget(self.page_management_roll_call)
-
- # 添加抽奖页面管理组件
- self.page_management_lottery = page_management_lottery(self)
- self.vBoxLayout.addWidget(self.page_management_lottery)
+ # 延迟创建子组件:先插入占位容器并注册创建工厂,避免一次性阻塞
+ self._deferred_factories = {}
+
+ def make_placeholder(attr_name: str):
+ w = QWidget()
+ w.setObjectName(attr_name)
+ layout = QVBoxLayout(w)
+ layout.setContentsMargins(0, 0, 0, 0)
+ self.vBoxLayout.addWidget(w)
+ return w
+
+ # create placeholders
+ self.page_management_roll_call = make_placeholder("page_management_roll_call")
+ self._deferred_factories["page_management_roll_call"] = (
+ lambda parent=self: page_management_roll_call(parent)
+ )
+
+ self.page_management_lottery = make_placeholder("page_management_lottery")
+ self._deferred_factories["page_management_lottery"] = (
+ lambda parent=self: page_management_lottery(parent)
+ )
+
+ self.page_management_custom = make_placeholder("page_management_custom")
+ self._deferred_factories["page_management_custom"] = (
+ lambda parent=self: page_management_custom(parent)
+ )
+
+ # 分批异步创建真实子组件,间隔以减少主线程瞬时负载
+ try:
+ for i, name in enumerate(list(self._deferred_factories.keys())):
+ QTimer.singleShot(150 * i, lambda n=name: self._create_deferred(n))
+ except Exception as e:
+ logger.error(f"调度延迟创建子组件失败: {e}")
+
+ def _create_deferred(self, name: str):
+ """按需创建延迟注册的子组件并替换占位容器"""
+ try:
+ factories = getattr(self, "_deferred_factories", {})
+ if name not in factories:
+ return
+ factory = factories.pop(name)
+ start = time.perf_counter()
+ real_widget = factory()
+ elapsed = time.perf_counter() - start
+ # 找到占位容器
+ placeholder = getattr(self, name, None)
+ if placeholder is None:
+ # 没有占位则直接插入
+ self.vBoxLayout.addWidget(real_widget)
+ else:
+ # 替换属性引用为真实 widget
+ # 保证 placeholder 有 layout
+ layout = placeholder.layout()
+ if layout is not None:
+ layout.addWidget(real_widget)
+ # 更新属性引用,方便后续直接访问
+ setattr(self, name, real_widget)
+
+ logger.debug(f"延迟创建子组件 {name} 耗时: {elapsed:.3f}s")
+ except Exception as e:
+ logger.error(f"创建子组件 {name} 失败: {e}")
- # 添加自定义抽页面管理组件
- self.page_management_custom = page_management_custom(self)
- self.vBoxLayout.addWidget(self.page_management_custom)
class page_management_roll_call(GroupHeaderCardWidget):
def __init__(self, parent=None):
@@ -51,85 +100,262 @@ def __init__(self, parent=None):
# 点名控制面板位置下拉框
self.roll_call_method_combo = ComboBox()
- self.roll_call_method_combo.addItems(get_content_combo_name_async("page_management", "roll_call_method"))
- self.roll_call_method_combo.setCurrentIndex(readme_settings_async("page_management", "roll_call_method"))
- self.roll_call_method_combo.currentIndexChanged.connect(lambda: update_settings("page_management", "roll_call_method", self.roll_call_method_combo.currentIndex()))
+ self.roll_call_method_combo.addItems(
+ get_content_combo_name_async("page_management", "roll_call_method")
+ )
+ self.roll_call_method_combo.setCurrentIndex(
+ readme_settings_async("page_management", "roll_call_method")
+ )
+ self.roll_call_method_combo.currentIndexChanged.connect(
+ lambda: update_settings(
+ "page_management",
+ "roll_call_method",
+ self.roll_call_method_combo.currentIndex(),
+ )
+ )
# 姓名设置按钮是否显示开关
self.show_name_button_switch = SwitchButton()
- self.show_name_button_switch.setOffText(get_content_switchbutton_name_async("page_management", "show_name", "disable"))
- self.show_name_button_switch.setOnText(get_content_switchbutton_name_async("page_management", "show_name", "enable"))
- self.show_name_button_switch.setChecked(readme_settings_async("page_management", "show_name"))
- self.show_name_button_switch.checkedChanged.connect(lambda: update_settings("page_management", "show_name", self.show_name_button_switch.isChecked()))
+ self.show_name_button_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "page_management", "show_name", "disable"
+ )
+ )
+ self.show_name_button_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "page_management", "show_name", "enable"
+ )
+ )
+ self.show_name_button_switch.setChecked(
+ readme_settings_async("page_management", "show_name")
+ )
+ self.show_name_button_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "page_management", "show_name", self.show_name_button_switch.isChecked()
+ )
+ )
# 重置已抽取名单按钮是否显示开关
self.reset_roll_call_button_switch = SwitchButton()
- self.reset_roll_call_button_switch.setOffText(get_content_switchbutton_name_async("page_management", "reset_roll_call", "disable"))
- self.reset_roll_call_button_switch.setOnText(get_content_switchbutton_name_async("page_management", "reset_roll_call", "enable"))
- self.reset_roll_call_button_switch.setChecked(readme_settings_async("page_management", "reset_roll_call"))
- self.reset_roll_call_button_switch.checkedChanged.connect(lambda: update_settings("page_management", "reset_roll_call", self.reset_roll_call_button_switch.isChecked()))
+ self.reset_roll_call_button_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "page_management", "reset_roll_call", "disable"
+ )
+ )
+ self.reset_roll_call_button_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "page_management", "reset_roll_call", "enable"
+ )
+ )
+ self.reset_roll_call_button_switch.setChecked(
+ readme_settings_async("page_management", "reset_roll_call")
+ )
+ self.reset_roll_call_button_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "page_management",
+ "reset_roll_call",
+ self.reset_roll_call_button_switch.isChecked(),
+ )
+ )
# 增加/减少抽取数量控制条是否显示开关
self.roll_call_quantity_control_switch = SwitchButton()
- self.roll_call_quantity_control_switch.setOffText(get_content_switchbutton_name_async("page_management", "roll_call_quantity_control", "disable"))
- self.roll_call_quantity_control_switch.setOnText(get_content_switchbutton_name_async("page_management", "roll_call_quantity_control", "enable"))
- self.roll_call_quantity_control_switch.setChecked(readme_settings_async("page_management", "roll_call_quantity_control"))
- self.roll_call_quantity_control_switch.checkedChanged.connect(lambda: update_settings("page_management", "roll_call_quantity_control", self.roll_call_quantity_control_switch.isChecked()))
+ self.roll_call_quantity_control_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "page_management", "roll_call_quantity_control", "disable"
+ )
+ )
+ self.roll_call_quantity_control_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "page_management", "roll_call_quantity_control", "enable"
+ )
+ )
+ self.roll_call_quantity_control_switch.setChecked(
+ readme_settings_async("page_management", "roll_call_quantity_control")
+ )
+ self.roll_call_quantity_control_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "page_management",
+ "roll_call_quantity_control",
+ self.roll_call_quantity_control_switch.isChecked(),
+ )
+ )
# 开始按钮是否显示开关
self.roll_call_start_button_switch = SwitchButton()
- self.roll_call_start_button_switch.setOffText(get_content_switchbutton_name_async("page_management", "roll_call_start_button", "disable"))
- self.roll_call_start_button_switch.setOnText(get_content_switchbutton_name_async("page_management", "roll_call_start_button", "enable"))
- self.roll_call_start_button_switch.setChecked(readme_settings_async("page_management", "roll_call_start_button"))
- self.roll_call_start_button_switch.checkedChanged.connect(lambda: update_settings("page_management", "roll_call_start_button", self.roll_call_start_button_switch.isChecked()))
+ self.roll_call_start_button_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "page_management", "roll_call_start_button", "disable"
+ )
+ )
+ self.roll_call_start_button_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "page_management", "roll_call_start_button", "enable"
+ )
+ )
+ self.roll_call_start_button_switch.setChecked(
+ readme_settings_async("page_management", "roll_call_start_button")
+ )
+ self.roll_call_start_button_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "page_management",
+ "roll_call_start_button",
+ self.roll_call_start_button_switch.isChecked(),
+ )
+ )
# 名单切换下拉框是否显示开关
self.roll_call_list_combo_switch = SwitchButton()
- self.roll_call_list_combo_switch.setOffText(get_content_switchbutton_name_async("page_management", "roll_call_list", "disable"))
- self.roll_call_list_combo_switch.setOnText(get_content_switchbutton_name_async("page_management", "roll_call_list", "enable"))
- self.roll_call_list_combo_switch.setChecked(readme_settings_async("page_management", "roll_call_list"))
- self.roll_call_list_combo_switch.checkedChanged.connect(lambda: update_settings("page_management", "roll_call_list", self.roll_call_list_combo_switch.isChecked()))
-
+ self.roll_call_list_combo_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "page_management", "roll_call_list", "disable"
+ )
+ )
+ self.roll_call_list_combo_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "page_management", "roll_call_list", "enable"
+ )
+ )
+ self.roll_call_list_combo_switch.setChecked(
+ readme_settings_async("page_management", "roll_call_list")
+ )
+ self.roll_call_list_combo_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "page_management",
+ "roll_call_list",
+ self.roll_call_list_combo_switch.isChecked(),
+ )
+ )
+
# 抽取范围下拉框是否显示开关
self.roll_call_range_combo_switch = SwitchButton()
- self.roll_call_range_combo_switch.setOffText(get_content_switchbutton_name_async("page_management", "roll_call_range", "disable"))
- self.roll_call_range_combo_switch.setOnText(get_content_switchbutton_name_async("page_management", "roll_call_range", "enable"))
- self.roll_call_range_combo_switch.setChecked(readme_settings_async("page_management", "roll_call_range"))
- self.roll_call_range_combo_switch.checkedChanged.connect(lambda: update_settings("page_management", "roll_call_range", self.roll_call_range_combo_switch.isChecked()))
+ self.roll_call_range_combo_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "page_management", "roll_call_range", "disable"
+ )
+ )
+ self.roll_call_range_combo_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "page_management", "roll_call_range", "enable"
+ )
+ )
+ self.roll_call_range_combo_switch.setChecked(
+ readme_settings_async("page_management", "roll_call_range")
+ )
+ self.roll_call_range_combo_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "page_management",
+ "roll_call_range",
+ self.roll_call_range_combo_switch.isChecked(),
+ )
+ )
# 抽取性别下拉框是否显示开关
self.roll_call_gender_combo_switch = SwitchButton()
- self.roll_call_gender_combo_switch.setOffText(get_content_switchbutton_name_async("page_management", "roll_call_gender", "disable"))
- self.roll_call_gender_combo_switch.setOnText(get_content_switchbutton_name_async("page_management", "roll_call_gender", "enable"))
- self.roll_call_gender_combo_switch.setChecked(readme_settings_async("page_management", "roll_call_gender"))
- self.roll_call_gender_combo_switch.checkedChanged.connect(lambda: update_settings("page_management", "roll_call_gender", self.roll_call_gender_combo_switch.isChecked()))
+ self.roll_call_gender_combo_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "page_management", "roll_call_gender", "disable"
+ )
+ )
+ self.roll_call_gender_combo_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "page_management", "roll_call_gender", "enable"
+ )
+ )
+ self.roll_call_gender_combo_switch.setChecked(
+ readme_settings_async("page_management", "roll_call_gender")
+ )
+ self.roll_call_gender_combo_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "page_management",
+ "roll_call_gender",
+ self.roll_call_gender_combo_switch.isChecked(),
+ )
+ )
# 班级人数/组数标签是否显示开关
self.roll_call_quantity_label_switch = SwitchButton()
- self.roll_call_quantity_label_switch.setOffText(get_content_switchbutton_name_async("page_management", "roll_call_quantity_label", "disable"))
- self.roll_call_quantity_label_switch.setOnText(get_content_switchbutton_name_async("page_management", "roll_call_quantity_label", "enable"))
- self.roll_call_quantity_label_switch.setChecked(readme_settings_async("page_management", "roll_call_quantity_label"))
- self.roll_call_quantity_label_switch.checkedChanged.connect(lambda: update_settings("page_management", "roll_call_quantity_label", self.roll_call_quantity_label_switch.isChecked()))
+ self.roll_call_quantity_label_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "page_management", "roll_call_quantity_label", "disable"
+ )
+ )
+ self.roll_call_quantity_label_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "page_management", "roll_call_quantity_label", "enable"
+ )
+ )
+ self.roll_call_quantity_label_switch.setChecked(
+ readme_settings_async("page_management", "roll_call_quantity_label")
+ )
+ self.roll_call_quantity_label_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "page_management",
+ "roll_call_quantity_label",
+ self.roll_call_quantity_label_switch.isChecked(),
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_window_multiple_swap_20_filled"),
- get_content_name_async("page_management", "roll_call_method"), get_content_description_async("page_management", "roll_call_method"), self.roll_call_method_combo)
- self.addGroup(get_theme_icon("ic_fluent_slide_text_edit_20_filled"),
- get_content_name_async("page_management", "show_name"), get_content_description_async("page_management", "show_name"), self.show_name_button_switch)
- self.addGroup(get_theme_icon("ic_fluent_arrow_reset_20_filled"),
- get_content_name_async("page_management", "reset_roll_call"), get_content_description_async("page_management", "reset_roll_call"), self.reset_roll_call_button_switch)
- self.addGroup(get_theme_icon("ic_fluent_arrow_autofit_content_20_filled"),
- get_content_name_async("page_management", "roll_call_quantity_control"), get_content_description_async("page_management", "roll_call_quantity_control"), self.roll_call_quantity_control_switch)
- self.addGroup(get_theme_icon("ic_fluent_slide_play_20_filled"),
- get_content_name_async("page_management", "roll_call_start_button"), get_content_description_async("page_management", "roll_call_start_button"), self.roll_call_start_button_switch)
- self.addGroup(get_theme_icon("ic_fluent_notepad_person_20_filled"),
- get_content_name_async("page_management", "roll_call_list"), get_content_description_async("page_management", "roll_call_list"), self.roll_call_list_combo_switch)
- self.addGroup(get_theme_icon("ic_fluent_convert_range_20_filled"),
- get_content_name_async("page_management", "roll_call_range"), get_content_description_async("page_management", "roll_call_range"), self.roll_call_range_combo_switch)
- self.addGroup(get_theme_icon("ic_fluent_video_person_sparkle_20_filled"),
- get_content_name_async("page_management", "roll_call_gender"), get_content_description_async("page_management", "roll_call_gender"), self.roll_call_gender_combo_switch)
- self.addGroup(get_theme_icon("ic_fluent_slide_text_person_20_filled"),
- get_content_name_async("page_management", "roll_call_quantity_label"), get_content_description_async("page_management", "roll_call_quantity_label"), self.roll_call_quantity_label_switch)
+ self.addGroup(
+ get_theme_icon("ic_fluent_window_multiple_swap_20_filled"),
+ get_content_name_async("page_management", "roll_call_method"),
+ get_content_description_async("page_management", "roll_call_method"),
+ self.roll_call_method_combo,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_slide_text_edit_20_filled"),
+ get_content_name_async("page_management", "show_name"),
+ get_content_description_async("page_management", "show_name"),
+ self.show_name_button_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_arrow_reset_20_filled"),
+ get_content_name_async("page_management", "reset_roll_call"),
+ get_content_description_async("page_management", "reset_roll_call"),
+ self.reset_roll_call_button_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_arrow_autofit_content_20_filled"),
+ get_content_name_async("page_management", "roll_call_quantity_control"),
+ get_content_description_async(
+ "page_management", "roll_call_quantity_control"
+ ),
+ self.roll_call_quantity_control_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_slide_play_20_filled"),
+ get_content_name_async("page_management", "roll_call_start_button"),
+ get_content_description_async("page_management", "roll_call_start_button"),
+ self.roll_call_start_button_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_notepad_person_20_filled"),
+ get_content_name_async("page_management", "roll_call_list"),
+ get_content_description_async("page_management", "roll_call_list"),
+ self.roll_call_list_combo_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_convert_range_20_filled"),
+ get_content_name_async("page_management", "roll_call_range"),
+ get_content_description_async("page_management", "roll_call_range"),
+ self.roll_call_range_combo_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_video_person_sparkle_20_filled"),
+ get_content_name_async("page_management", "roll_call_gender"),
+ get_content_description_async("page_management", "roll_call_gender"),
+ self.roll_call_gender_combo_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_slide_text_person_20_filled"),
+ get_content_name_async("page_management", "roll_call_quantity_label"),
+ get_content_description_async(
+ "page_management", "roll_call_quantity_label"
+ ),
+ self.roll_call_quantity_label_switch,
+ )
+
class page_management_lottery(GroupHeaderCardWidget):
def __init__(self, parent=None):
@@ -139,67 +365,203 @@ def __init__(self, parent=None):
# 抽奖控制面板位置下拉框
self.lottery_method_combo = ComboBox()
- self.lottery_method_combo.addItems(get_content_combo_name_async("page_management", "lottery_method"))
- self.lottery_method_combo.setCurrentIndex(readme_settings_async("page_management", "lottery_method"))
- self.lottery_method_combo.currentIndexChanged.connect(lambda: update_settings("page_management", "lottery_method", self.lottery_method_combo.currentIndex()))
+ self.lottery_method_combo.addItems(
+ get_content_combo_name_async("page_management", "lottery_method")
+ )
+ self.lottery_method_combo.setCurrentIndex(
+ readme_settings_async("page_management", "lottery_method")
+ )
+ self.lottery_method_combo.currentIndexChanged.connect(
+ lambda: update_settings(
+ "page_management",
+ "lottery_method",
+ self.lottery_method_combo.currentIndex(),
+ )
+ )
# 奖品名称设置按钮是否显示开关
self.show_lottery_name_button_switch = SwitchButton()
- self.show_lottery_name_button_switch.setOffText(get_content_switchbutton_name_async("page_management", "show_lottery_name", "disable"))
- self.show_lottery_name_button_switch.setOnText(get_content_switchbutton_name_async("page_management", "show_lottery_name", "enable"))
- self.show_lottery_name_button_switch.setChecked(readme_settings_async("page_management", "show_lottery_name"))
- self.show_lottery_name_button_switch.checkedChanged.connect(lambda: update_settings("page_management", "show_lottery_name", self.show_lottery_name_button_switch.isChecked()))
+ self.show_lottery_name_button_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "page_management", "show_lottery_name", "disable"
+ )
+ )
+ self.show_lottery_name_button_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "page_management", "show_lottery_name", "enable"
+ )
+ )
+ self.show_lottery_name_button_switch.setChecked(
+ readme_settings_async("page_management", "show_lottery_name")
+ )
+ self.show_lottery_name_button_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "page_management",
+ "show_lottery_name",
+ self.show_lottery_name_button_switch.isChecked(),
+ )
+ )
# 重置已抽取名单按钮是否显示开关
self.reset_lottery_button_switch = SwitchButton()
- self.reset_lottery_button_switch.setOffText(get_content_switchbutton_name_async("page_management", "reset_lottery", "disable"))
- self.reset_lottery_button_switch.setOnText(get_content_switchbutton_name_async("page_management", "reset_lottery", "enable"))
- self.reset_lottery_button_switch.setChecked(readme_settings_async("page_management", "reset_lottery"))
- self.reset_lottery_button_switch.checkedChanged.connect(lambda: update_settings("page_management", "reset_lottery", self.reset_lottery_button_switch.isChecked()))
+ self.reset_lottery_button_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "page_management", "reset_lottery", "disable"
+ )
+ )
+ self.reset_lottery_button_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "page_management", "reset_lottery", "enable"
+ )
+ )
+ self.reset_lottery_button_switch.setChecked(
+ readme_settings_async("page_management", "reset_lottery")
+ )
+ self.reset_lottery_button_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "page_management",
+ "reset_lottery",
+ self.reset_lottery_button_switch.isChecked(),
+ )
+ )
# 增加/减少抽取数量控制条是否显示开关
self.lottery_quantity_control_switch = SwitchButton()
- self.lottery_quantity_control_switch.setOffText(get_content_switchbutton_name_async("page_management", "lottery_quantity_control", "disable"))
- self.lottery_quantity_control_switch.setOnText(get_content_switchbutton_name_async("page_management", "lottery_quantity_control", "enable"))
- self.lottery_quantity_control_switch.setChecked(readme_settings_async("page_management", "lottery_quantity_control"))
- self.lottery_quantity_control_switch.checkedChanged.connect(lambda: update_settings("page_management", "lottery_quantity_control", self.lottery_quantity_control_switch.isChecked()))
+ self.lottery_quantity_control_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "page_management", "lottery_quantity_control", "disable"
+ )
+ )
+ self.lottery_quantity_control_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "page_management", "lottery_quantity_control", "enable"
+ )
+ )
+ self.lottery_quantity_control_switch.setChecked(
+ readme_settings_async("page_management", "lottery_quantity_control")
+ )
+ self.lottery_quantity_control_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "page_management",
+ "lottery_quantity_control",
+ self.lottery_quantity_control_switch.isChecked(),
+ )
+ )
# 开始按钮是否显示开关
self.lottery_start_button_switch = SwitchButton()
- self.lottery_start_button_switch.setOffText(get_content_switchbutton_name_async("page_management", "lottery_start_button", "disable"))
- self.lottery_start_button_switch.setOnText(get_content_switchbutton_name_async("page_management", "lottery_start_button", "enable"))
- self.lottery_start_button_switch.setChecked(readme_settings_async("page_management", "lottery_start_button"))
- self.lottery_start_button_switch.checkedChanged.connect(lambda: update_settings("page_management", "lottery_start_button", self.lottery_start_button_switch.isChecked()))
+ self.lottery_start_button_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "page_management", "lottery_start_button", "disable"
+ )
+ )
+ self.lottery_start_button_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "page_management", "lottery_start_button", "enable"
+ )
+ )
+ self.lottery_start_button_switch.setChecked(
+ readme_settings_async("page_management", "lottery_start_button")
+ )
+ self.lottery_start_button_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "page_management",
+ "lottery_start_button",
+ self.lottery_start_button_switch.isChecked(),
+ )
+ )
# 名单切换下拉框是否显示开关
self.lottery_list_combo_switch = SwitchButton()
- self.lottery_list_combo_switch.setOffText(get_content_switchbutton_name_async("page_management", "lottery_list", "disable"))
- self.lottery_list_combo_switch.setOnText(get_content_switchbutton_name_async("page_management", "lottery_list", "enable"))
- self.lottery_list_combo_switch.setChecked(readme_settings_async("page_management", "lottery_list"))
- self.lottery_list_combo_switch.checkedChanged.connect(lambda: update_settings("page_management", "lottery_list", self.lottery_list_combo_switch.isChecked()))
-
+ self.lottery_list_combo_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "page_management", "lottery_list", "disable"
+ )
+ )
+ self.lottery_list_combo_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "page_management", "lottery_list", "enable"
+ )
+ )
+ self.lottery_list_combo_switch.setChecked(
+ readme_settings_async("page_management", "lottery_list")
+ )
+ self.lottery_list_combo_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "page_management",
+ "lottery_list",
+ self.lottery_list_combo_switch.isChecked(),
+ )
+ )
+
# 班级人数/组数标签是否显示开关
self.lottery_quantity_label_switch = SwitchButton()
- self.lottery_quantity_label_switch.setOffText(get_content_switchbutton_name_async("page_management", "lottery_quantity_label", "disable"))
- self.lottery_quantity_label_switch.setOnText(get_content_switchbutton_name_async("page_management", "lottery_quantity_label", "enable"))
- self.lottery_quantity_label_switch.setChecked(readme_settings_async("page_management", "lottery_quantity_label"))
- self.lottery_quantity_label_switch.checkedChanged.connect(lambda: update_settings("page_management", "lottery_quantity_label", self.lottery_quantity_label_switch.isChecked()))
+ self.lottery_quantity_label_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "page_management", "lottery_quantity_label", "disable"
+ )
+ )
+ self.lottery_quantity_label_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "page_management", "lottery_quantity_label", "enable"
+ )
+ )
+ self.lottery_quantity_label_switch.setChecked(
+ readme_settings_async("page_management", "lottery_quantity_label")
+ )
+ self.lottery_quantity_label_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "page_management",
+ "lottery_quantity_label",
+ self.lottery_quantity_label_switch.isChecked(),
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_window_multiple_swap_20_filled"),
- get_content_name_async("page_management", "lottery_method"), get_content_description_async("page_management", "lottery_method"), self.lottery_method_combo)
- self.addGroup(get_theme_icon("ic_fluent_slide_text_edit_20_filled"),
- get_content_name_async("page_management", "show_lottery_name"), get_content_description_async("page_management", "show_lottery_name"), self.show_lottery_name_button_switch)
- self.addGroup(get_theme_icon("ic_fluent_arrow_reset_20_filled"),
- get_content_name_async("page_management", "reset_lottery"), get_content_description_async("page_management", "reset_lottery"), self.reset_lottery_button_switch)
- self.addGroup(get_theme_icon("ic_fluent_arrow_autofit_content_20_filled"),
- get_content_name_async("page_management", "lottery_quantity_control"), get_content_description_async("page_management", "lottery_quantity_control"), self.lottery_quantity_control_switch)
- self.addGroup(get_theme_icon("ic_fluent_slide_play_20_filled"),
- get_content_name_async("page_management", "lottery_start_button"), get_content_description_async("page_management", "lottery_start_button"), self.lottery_start_button_switch)
- self.addGroup(get_theme_icon("ic_fluent_notepad_person_20_filled"),
- get_content_name_async("page_management", "lottery_list"), get_content_description_async("page_management", "lottery_list"), self.lottery_list_combo_switch)
- self.addGroup(get_theme_icon("ic_fluent_slide_text_person_20_filled"),
- get_content_name_async("page_management", "lottery_quantity_label"), get_content_description_async("page_management", "lottery_quantity_label"), self.lottery_quantity_label_switch)
+ self.addGroup(
+ get_theme_icon("ic_fluent_window_multiple_swap_20_filled"),
+ get_content_name_async("page_management", "lottery_method"),
+ get_content_description_async("page_management", "lottery_method"),
+ self.lottery_method_combo,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_slide_text_edit_20_filled"),
+ get_content_name_async("page_management", "show_lottery_name"),
+ get_content_description_async("page_management", "show_lottery_name"),
+ self.show_lottery_name_button_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_arrow_reset_20_filled"),
+ get_content_name_async("page_management", "reset_lottery"),
+ get_content_description_async("page_management", "reset_lottery"),
+ self.reset_lottery_button_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_arrow_autofit_content_20_filled"),
+ get_content_name_async("page_management", "lottery_quantity_control"),
+ get_content_description_async(
+ "page_management", "lottery_quantity_control"
+ ),
+ self.lottery_quantity_control_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_slide_play_20_filled"),
+ get_content_name_async("page_management", "lottery_start_button"),
+ get_content_description_async("page_management", "lottery_start_button"),
+ self.lottery_start_button_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_notepad_person_20_filled"),
+ get_content_name_async("page_management", "lottery_list"),
+ get_content_description_async("page_management", "lottery_list"),
+ self.lottery_list_combo_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_slide_text_person_20_filled"),
+ get_content_name_async("page_management", "lottery_quantity_label"),
+ get_content_description_async("page_management", "lottery_quantity_label"),
+ self.lottery_quantity_label_switch,
+ )
class page_management_custom(GroupHeaderCardWidget):
@@ -210,82 +572,256 @@ def __init__(self, parent=None):
# 点名控制面板位置下拉框
self.custom_method_combo = ComboBox()
- self.custom_method_combo.addItems(get_content_combo_name_async("page_management", "custom_method"))
- self.custom_method_combo.setCurrentIndex(readme_settings_async("page_management", "custom_method"))
- self.custom_method_combo.currentIndexChanged.connect(lambda: update_settings("page_management", "custom_method", self.custom_method_combo.currentIndex()))
+ self.custom_method_combo.addItems(
+ get_content_combo_name_async("page_management", "custom_method")
+ )
+ self.custom_method_combo.setCurrentIndex(
+ readme_settings_async("page_management", "custom_method")
+ )
+ self.custom_method_combo.currentIndexChanged.connect(
+ lambda: update_settings(
+ "page_management",
+ "custom_method",
+ self.custom_method_combo.currentIndex(),
+ )
+ )
# 重置已抽取名单按钮是否显示开关
self.reset_custom_button_switch = SwitchButton()
- self.reset_custom_button_switch.setOffText(get_content_switchbutton_name_async("page_management", "reset_custom", "disable"))
- self.reset_custom_button_switch.setOnText(get_content_switchbutton_name_async("page_management", "reset_custom", "enable"))
- self.reset_custom_button_switch.setChecked(readme_settings_async("page_management", "reset_custom"))
- self.reset_custom_button_switch.checkedChanged.connect(lambda: update_settings("page_management", "reset_custom", self.reset_custom_button_switch.isChecked()))
+ self.reset_custom_button_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "page_management", "reset_custom", "disable"
+ )
+ )
+ self.reset_custom_button_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "page_management", "reset_custom", "enable"
+ )
+ )
+ self.reset_custom_button_switch.setChecked(
+ readme_settings_async("page_management", "reset_custom")
+ )
+ self.reset_custom_button_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "page_management",
+ "reset_custom",
+ self.reset_custom_button_switch.isChecked(),
+ )
+ )
# 增加/减少抽取数量控制条是否显示开关
self.custom_quantity_control_switch = SwitchButton()
- self.custom_quantity_control_switch.setOffText(get_content_switchbutton_name_async("page_management", "custom_quantity_control", "disable"))
- self.custom_quantity_control_switch.setOnText(get_content_switchbutton_name_async("page_management", "custom_quantity_control", "enable"))
- self.custom_quantity_control_switch.setChecked(readme_settings_async("page_management", "custom_quantity_control"))
- self.custom_quantity_control_switch.checkedChanged.connect(lambda: update_settings("page_management", "custom_quantity_control", self.custom_quantity_control_switch.isChecked()))
+ self.custom_quantity_control_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "page_management", "custom_quantity_control", "disable"
+ )
+ )
+ self.custom_quantity_control_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "page_management", "custom_quantity_control", "enable"
+ )
+ )
+ self.custom_quantity_control_switch.setChecked(
+ readme_settings_async("page_management", "custom_quantity_control")
+ )
+ self.custom_quantity_control_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "page_management",
+ "custom_quantity_control",
+ self.custom_quantity_control_switch.isChecked(),
+ )
+ )
# 开始按钮是否显示开关
self.custom_start_button_switch = SwitchButton()
- self.custom_start_button_switch.setOffText(get_content_switchbutton_name_async("page_management", "custom_start_button", "disable"))
- self.custom_start_button_switch.setOnText(get_content_switchbutton_name_async("page_management", "custom_start_button", "enable"))
- self.custom_start_button_switch.setChecked(readme_settings_async("page_management", "custom_start_button"))
- self.custom_start_button_switch.checkedChanged.connect(lambda: update_settings("page_management", "custom_start_button", self.custom_start_button_switch.isChecked()))
+ self.custom_start_button_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "page_management", "custom_start_button", "disable"
+ )
+ )
+ self.custom_start_button_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "page_management", "custom_start_button", "enable"
+ )
+ )
+ self.custom_start_button_switch.setChecked(
+ readme_settings_async("page_management", "custom_start_button")
+ )
+ self.custom_start_button_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "page_management",
+ "custom_start_button",
+ self.custom_start_button_switch.isChecked(),
+ )
+ )
# 名单切换下拉框是否显示开关
self.custom_list_combo_switch = SwitchButton()
- self.custom_list_combo_switch.setOffText(get_content_switchbutton_name_async("page_management", "custom_list", "disable"))
- self.custom_list_combo_switch.setOnText(get_content_switchbutton_name_async("page_management", "custom_list", "enable"))
- self.custom_list_combo_switch.setChecked(readme_settings_async("page_management", "custom_list"))
- self.custom_list_combo_switch.checkedChanged.connect(lambda: update_settings("page_management", "custom_list", self.custom_list_combo_switch.isChecked()))
-
+ self.custom_list_combo_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "page_management", "custom_list", "disable"
+ )
+ )
+ self.custom_list_combo_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "page_management", "custom_list", "enable"
+ )
+ )
+ self.custom_list_combo_switch.setChecked(
+ readme_settings_async("page_management", "custom_list")
+ )
+ self.custom_list_combo_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "page_management",
+ "custom_list",
+ self.custom_list_combo_switch.isChecked(),
+ )
+ )
+
# 抽取范围的起始值下拉框是否显示开关
self.custom_range_start_combo_switch = SwitchButton()
- self.custom_range_start_combo_switch.setOffText(get_content_switchbutton_name_async("page_management", "custom_range_start", "disable"))
- self.custom_range_start_combo_switch.setOnText(get_content_switchbutton_name_async("page_management", "custom_range_start", "enable"))
- self.custom_range_start_combo_switch.setChecked(readme_settings_async("page_management", "custom_range_start"))
- self.custom_range_start_combo_switch.checkedChanged.connect(lambda: update_settings("page_management", "custom_range_start", self.custom_range_start_combo_switch.isChecked()))
+ self.custom_range_start_combo_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "page_management", "custom_range_start", "disable"
+ )
+ )
+ self.custom_range_start_combo_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "page_management", "custom_range_start", "enable"
+ )
+ )
+ self.custom_range_start_combo_switch.setChecked(
+ readme_settings_async("page_management", "custom_range_start")
+ )
+ self.custom_range_start_combo_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "page_management",
+ "custom_range_start",
+ self.custom_range_start_combo_switch.isChecked(),
+ )
+ )
# 抽取范围的终止值下拉框是否显示开关
self.custom_range_end_combo_switch = SwitchButton()
- self.custom_range_end_combo_switch.setOffText(get_content_switchbutton_name_async("page_management", "custom_range_end", "disable"))
- self.custom_range_end_combo_switch.setOnText(get_content_switchbutton_name_async("page_management", "custom_range_end", "enable"))
- self.custom_range_end_combo_switch.setChecked(readme_settings_async("page_management", "custom_range_end"))
- self.custom_range_end_combo_switch.checkedChanged.connect(lambda: update_settings("page_management", "custom_range_end", self.custom_range_end_combo_switch.isChecked()))
+ self.custom_range_end_combo_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "page_management", "custom_range_end", "disable"
+ )
+ )
+ self.custom_range_end_combo_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "page_management", "custom_range_end", "enable"
+ )
+ )
+ self.custom_range_end_combo_switch.setChecked(
+ readme_settings_async("page_management", "custom_range_end")
+ )
+ self.custom_range_end_combo_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "page_management",
+ "custom_range_end",
+ self.custom_range_end_combo_switch.isChecked(),
+ )
+ )
# 抽取模式选择下拉框是否显示开关
self.draw_custom_method_combo_switch = SwitchButton()
- self.draw_custom_method_combo_switch.setOffText(get_content_switchbutton_name_async("page_management", "`draw_custom_method`", "disable"))
- self.draw_custom_method_combo_switch.setOnText(get_content_switchbutton_name_async("page_management", "draw_custom_method", "enable"))
- self.draw_custom_method_combo_switch.setChecked(readme_settings_async("page_management", "draw_custom_method"))
- self.draw_custom_method_combo_switch.checkedChanged.connect(lambda: update_settings("page_management", "draw_custom_method", self.draw_custom_method_combo_switch.isChecked()))
+ self.draw_custom_method_combo_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "page_management", "`draw_custom_method`", "disable"
+ )
+ )
+ self.draw_custom_method_combo_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "page_management", "draw_custom_method", "enable"
+ )
+ )
+ self.draw_custom_method_combo_switch.setChecked(
+ readme_settings_async("page_management", "draw_custom_method")
+ )
+ self.draw_custom_method_combo_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "page_management",
+ "draw_custom_method",
+ self.draw_custom_method_combo_switch.isChecked(),
+ )
+ )
# 数量标签是否显示开关
self.custom_quantity_label_switch = SwitchButton()
- self.custom_quantity_label_switch.setOffText(get_content_switchbutton_name_async("page_management", "custom_quantity_label", "disable"))
- self.custom_quantity_label_switch.setOnText(get_content_switchbutton_name_async("page_management", "custom_quantity_label", "enable"))
- self.custom_quantity_label_switch.setChecked(readme_settings_async("page_management", "custom_quantity_label"))
- self.custom_quantity_label_switch.checkedChanged.connect(lambda: update_settings("page_management", "custom_quantity_label", self.custom_quantity_label_switch.isChecked()))
+ self.custom_quantity_label_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "page_management", "custom_quantity_label", "disable"
+ )
+ )
+ self.custom_quantity_label_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "page_management", "custom_quantity_label", "enable"
+ )
+ )
+ self.custom_quantity_label_switch.setChecked(
+ readme_settings_async("page_management", "custom_quantity_label")
+ )
+ self.custom_quantity_label_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "page_management",
+ "custom_quantity_label",
+ self.custom_quantity_label_switch.isChecked(),
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_window_multiple_swap_20_filled"),
- get_content_name_async("page_management", "custom_method"), get_content_description_async("page_management", "custom_method"), self.custom_method_combo)
- self.addGroup(get_theme_icon("ic_fluent_arrow_reset_20_filled"),
- get_content_name_async("page_management", "reset_custom"), get_content_description_async("page_management", "reset_custom"), self.reset_custom_button_switch)
- self.addGroup(get_theme_icon("ic_fluent_arrow_autofit_content_20_filled"),
- get_content_name_async("page_management", "custom_quantity_control"), get_content_description_async("page_management", "custom_quantity_control"), self.custom_quantity_control_switch)
- self.addGroup(get_theme_icon("ic_fluent_slide_play_20_filled"),
- get_content_name_async("page_management", "custom_start_button"), get_content_description_async("page_management", "custom_start_button"), self.custom_start_button_switch)
- self.addGroup(get_theme_icon("ic_fluent_notepad_person_20_filled"),
- get_content_name_async("page_management", "custom_list"), get_content_description_async("page_management", "custom_list"), self.custom_list_combo_switch)
- self.addGroup(get_theme_icon("ic_fluent_slide_text_person_20_filled"),
- get_content_name_async("page_management", "custom_range_start"), get_content_description_async("page_management", "custom_range_start"), self.custom_range_start_combo_switch)
- self.addGroup(get_theme_icon("ic_fluent_slide_text_person_20_filled"),
- get_content_name_async("page_management", "custom_range_end"), get_content_description_async("page_management", "custom_range_end"), self.custom_range_end_combo_switch)
- self.addGroup(get_theme_icon("ic_fluent_slide_text_person_20_filled"),
- get_content_name_async("page_management", "draw_custom_method"), get_content_description_async("page_management", "draw_custom_method"), self.draw_custom_method_combo_switch)
- self.addGroup(get_theme_icon("ic_fluent_slide_text_person_20_filled"),
- get_content_name_async("page_management", "custom_quantity_label"), get_content_description_async("page_management", "custom_quantity_label"), self.custom_quantity_label_switch)
+ self.addGroup(
+ get_theme_icon("ic_fluent_window_multiple_swap_20_filled"),
+ get_content_name_async("page_management", "custom_method"),
+ get_content_description_async("page_management", "custom_method"),
+ self.custom_method_combo,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_arrow_reset_20_filled"),
+ get_content_name_async("page_management", "reset_custom"),
+ get_content_description_async("page_management", "reset_custom"),
+ self.reset_custom_button_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_arrow_autofit_content_20_filled"),
+ get_content_name_async("page_management", "custom_quantity_control"),
+ get_content_description_async("page_management", "custom_quantity_control"),
+ self.custom_quantity_control_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_slide_play_20_filled"),
+ get_content_name_async("page_management", "custom_start_button"),
+ get_content_description_async("page_management", "custom_start_button"),
+ self.custom_start_button_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_notepad_person_20_filled"),
+ get_content_name_async("page_management", "custom_list"),
+ get_content_description_async("page_management", "custom_list"),
+ self.custom_list_combo_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_slide_text_person_20_filled"),
+ get_content_name_async("page_management", "custom_range_start"),
+ get_content_description_async("page_management", "custom_range_start"),
+ self.custom_range_start_combo_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_slide_text_person_20_filled"),
+ get_content_name_async("page_management", "custom_range_end"),
+ get_content_description_async("page_management", "custom_range_end"),
+ self.custom_range_end_combo_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_slide_text_person_20_filled"),
+ get_content_name_async("page_management", "draw_custom_method"),
+ get_content_description_async("page_management", "draw_custom_method"),
+ self.draw_custom_method_combo_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_slide_text_person_20_filled"),
+ get_content_name_async("page_management", "custom_quantity_label"),
+ get_content_description_async("page_management", "custom_quantity_label"),
+ self.custom_quantity_label_switch,
+ )
diff --git a/app/view/settings/custom_settings/sidebar_tray_management.py b/app/view/settings/custom_settings/sidebar_tray_management.py
index 535995a0..f5419868 100644
--- a/app/view/settings/custom_settings/sidebar_tray_management.py
+++ b/app/view/settings/custom_settings/sidebar_tray_management.py
@@ -1,16 +1,11 @@
# ==================================================
# 导入库
# ==================================================
-import json
-import os
-import sys
-import subprocess
-
-from loguru import logger
-from PyQt6.QtWidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
+
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from PySide6.QtNetwork import *
from qfluentwidgets import *
from app.tools.variable import *
@@ -20,6 +15,7 @@
from app.tools.settings_access import *
from app.Language.obtain_language import *
+
# ==================================================
# 页面管理
# ==================================================
@@ -43,6 +39,7 @@ def __init__(self, parent=None):
self.tray_management = tray_management(self)
self.vBoxLayout.addWidget(self.tray_management)
+
class sidebar_management_window(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
@@ -51,45 +48,144 @@ def __init__(self, parent=None):
# 点名侧边栏位置下拉框
self.roll_call_sidebar_position_comboBox = ComboBox(self)
- self.roll_call_sidebar_position_comboBox.addItems(get_content_combo_name_async("sidebar_management_window", "roll_call_sidebar_position"))
- self.roll_call_sidebar_position_comboBox.setCurrentIndex(readme_settings_async("sidebar_management_window", "roll_call_sidebar_position"))
- self.roll_call_sidebar_position_comboBox.currentIndexChanged.connect(lambda: update_settings("sidebar_management_window", "roll_call_sidebar_position", self.roll_call_sidebar_position_comboBox.currentIndex()))
+ self.roll_call_sidebar_position_comboBox.addItems(
+ get_content_combo_name_async(
+ "sidebar_management_window", "roll_call_sidebar_position"
+ )
+ )
+ self.roll_call_sidebar_position_comboBox.setCurrentIndex(
+ readme_settings_async(
+ "sidebar_management_window", "roll_call_sidebar_position"
+ )
+ )
+ self.roll_call_sidebar_position_comboBox.currentIndexChanged.connect(
+ lambda: update_settings(
+ "sidebar_management_window",
+ "roll_call_sidebar_position",
+ self.roll_call_sidebar_position_comboBox.currentIndex(),
+ )
+ )
# 自定义抽侧边栏位置下拉框
self.custom_roll_call_sidebar_position_comboBox = ComboBox(self)
- self.custom_roll_call_sidebar_position_comboBox.addItems(get_content_combo_name_async("sidebar_management_window", "custom_roll_call_sidebar_position"))
- self.custom_roll_call_sidebar_position_comboBox.setCurrentIndex(readme_settings_async("sidebar_management_window", "custom_roll_call_sidebar_position"))
- self.custom_roll_call_sidebar_position_comboBox.currentIndexChanged.connect(lambda: update_settings("sidebar_management_window", "custom_roll_call_sidebar_position", self.custom_roll_call_sidebar_position_comboBox.currentIndex()))
+ self.custom_roll_call_sidebar_position_comboBox.addItems(
+ get_content_combo_name_async(
+ "sidebar_management_window", "custom_roll_call_sidebar_position"
+ )
+ )
+ self.custom_roll_call_sidebar_position_comboBox.setCurrentIndex(
+ readme_settings_async(
+ "sidebar_management_window", "custom_roll_call_sidebar_position"
+ )
+ )
+ self.custom_roll_call_sidebar_position_comboBox.currentIndexChanged.connect(
+ lambda: update_settings(
+ "sidebar_management_window",
+ "custom_roll_call_sidebar_position",
+ self.custom_roll_call_sidebar_position_comboBox.currentIndex(),
+ )
+ )
# 抽奖侧边栏位置下拉框
self.lottery_sidebar_position_comboBox = ComboBox(self)
- self.lottery_sidebar_position_comboBox.addItems(get_content_combo_name_async("sidebar_management_window", "lottery_sidebar_position"))
- self.lottery_sidebar_position_comboBox.setCurrentIndex(readme_settings_async("sidebar_management_window", "lottery_sidebar_position"))
- self.lottery_sidebar_position_comboBox.currentIndexChanged.connect(lambda: update_settings("sidebar_management_window", "lottery_sidebar_position", self.lottery_sidebar_position_comboBox.currentIndex()))
+ self.lottery_sidebar_position_comboBox.addItems(
+ get_content_combo_name_async(
+ "sidebar_management_window", "lottery_sidebar_position"
+ )
+ )
+ self.lottery_sidebar_position_comboBox.setCurrentIndex(
+ readme_settings_async(
+ "sidebar_management_window", "lottery_sidebar_position"
+ )
+ )
+ self.lottery_sidebar_position_comboBox.currentIndexChanged.connect(
+ lambda: update_settings(
+ "sidebar_management_window",
+ "lottery_sidebar_position",
+ self.lottery_sidebar_position_comboBox.currentIndex(),
+ )
+ )
# 主窗口历史记录下拉框
self.main_window_history_comboBox = ComboBox(self)
- self.main_window_history_comboBox.addItems(get_content_combo_name_async("sidebar_management_window", "main_window_history"))
- self.main_window_history_comboBox.setCurrentIndex(readme_settings_async("sidebar_management_window", "main_window_history"))
- self.main_window_history_comboBox.currentIndexChanged.connect(lambda: update_settings("sidebar_management_window", "main_window_history", self.main_window_history_comboBox.currentIndex()))
+ self.main_window_history_comboBox.addItems(
+ get_content_combo_name_async(
+ "sidebar_management_window", "main_window_history"
+ )
+ )
+ self.main_window_history_comboBox.setCurrentIndex(
+ readme_settings_async("sidebar_management_window", "main_window_history")
+ )
+ self.main_window_history_comboBox.currentIndexChanged.connect(
+ lambda: update_settings(
+ "sidebar_management_window",
+ "main_window_history",
+ self.main_window_history_comboBox.currentIndex(),
+ )
+ )
# 设置图标下拉框
self.settings_icon_comboBox = ComboBox(self)
- self.settings_icon_comboBox.addItems(get_content_combo_name_async("sidebar_management_window", "settings_icon"))
- self.settings_icon_comboBox.setCurrentIndex(readme_settings_async("sidebar_management_window", "settings_icon"))
- self.settings_icon_comboBox.currentIndexChanged.connect(lambda: update_settings("sidebar_management_window", "settings_icon", self.settings_icon_comboBox.currentIndex()))
+ self.settings_icon_comboBox.addItems(
+ get_content_combo_name_async("sidebar_management_window", "settings_icon")
+ )
+ self.settings_icon_comboBox.setCurrentIndex(
+ readme_settings_async("sidebar_management_window", "settings_icon")
+ )
+ self.settings_icon_comboBox.currentIndexChanged.connect(
+ lambda: update_settings(
+ "sidebar_management_window",
+ "settings_icon",
+ self.settings_icon_comboBox.currentIndex(),
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_people_community_20_filled"),
- get_content_name_async("sidebar_management_window", "roll_call_sidebar_position"), get_content_description_async("sidebar_management_window", "roll_call_sidebar_position"), self.roll_call_sidebar_position_comboBox)
- self.addGroup(get_theme_icon("ic_fluent_receipt_20_filled"),
- get_content_name_async("sidebar_management_window", "custom_roll_call_sidebar_position"), get_content_description_async("sidebar_management_window", "custom_roll_call_sidebar_position"), self.custom_roll_call_sidebar_position_comboBox)
- self.addGroup(get_theme_icon("ic_fluent_reward_20_filled"),
- get_content_name_async("sidebar_management_window", "lottery_sidebar_position"), get_content_description_async("sidebar_management_window", "lottery_sidebar_position"), self.lottery_sidebar_position_comboBox)
- self.addGroup(get_theme_icon("ic_fluent_chat_history_20_filled"),
- get_content_name_async("sidebar_management_window", "main_window_history"), get_content_description_async("sidebar_management_window", "main_window_history"), self.main_window_history_comboBox)
- self.addGroup(get_theme_icon("ic_fluent_settings_20_filled"),
- get_content_name_async("sidebar_management_window", "settings_icon"), get_content_description_async("sidebar_management_window", "settings_icon"), self.settings_icon_comboBox)
+ self.addGroup(
+ get_theme_icon("ic_fluent_people_community_20_filled"),
+ get_content_name_async(
+ "sidebar_management_window", "roll_call_sidebar_position"
+ ),
+ get_content_description_async(
+ "sidebar_management_window", "roll_call_sidebar_position"
+ ),
+ self.roll_call_sidebar_position_comboBox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_receipt_20_filled"),
+ get_content_name_async(
+ "sidebar_management_window", "custom_roll_call_sidebar_position"
+ ),
+ get_content_description_async(
+ "sidebar_management_window", "custom_roll_call_sidebar_position"
+ ),
+ self.custom_roll_call_sidebar_position_comboBox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_reward_20_filled"),
+ get_content_name_async(
+ "sidebar_management_window", "lottery_sidebar_position"
+ ),
+ get_content_description_async(
+ "sidebar_management_window", "lottery_sidebar_position"
+ ),
+ self.lottery_sidebar_position_comboBox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_chat_history_20_filled"),
+ get_content_name_async("sidebar_management_window", "main_window_history"),
+ get_content_description_async(
+ "sidebar_management_window", "main_window_history"
+ ),
+ self.main_window_history_comboBox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_settings_20_filled"),
+ get_content_name_async("sidebar_management_window", "settings_icon"),
+ get_content_description_async("sidebar_management_window", "settings_icon"),
+ self.settings_icon_comboBox,
+ )
+
class sidebar_management_settings(GroupHeaderCardWidget):
def __init__(self, parent=None):
@@ -99,85 +195,256 @@ def __init__(self, parent=None):
# 主页下拉框
self.home_comboBox = ComboBox(self)
- self.home_comboBox.addItems(get_content_combo_name_async("sidebar_management_settings", "home"))
- self.home_comboBox.setCurrentIndex(readme_settings_async("sidebar_management_settings", "home"))
- self.home_comboBox.currentIndexChanged.connect(lambda: update_settings("sidebar_management_settings", "home", self.home_comboBox.currentIndex()))
+ self.home_comboBox.addItems(
+ get_content_combo_name_async("sidebar_management_settings", "home")
+ )
+ self.home_comboBox.setCurrentIndex(
+ readme_settings_async("sidebar_management_settings", "home")
+ )
+ self.home_comboBox.currentIndexChanged.connect(
+ lambda: update_settings(
+ "sidebar_management_settings", "home", self.home_comboBox.currentIndex()
+ )
+ )
# 基础设置下拉框
self.base_settings_comboBox = ComboBox(self)
- self.base_settings_comboBox.addItems(get_content_combo_name_async("sidebar_management_settings", "base_settings"))
- self.base_settings_comboBox.setCurrentIndex(readme_settings_async("sidebar_management_settings", "base_settings"))
- self.base_settings_comboBox.currentIndexChanged.connect(lambda: update_settings("sidebar_management_settings", "base_settings", self.base_settings_comboBox.currentIndex()))
+ self.base_settings_comboBox.addItems(
+ get_content_combo_name_async("sidebar_management_settings", "base_settings")
+ )
+ self.base_settings_comboBox.setCurrentIndex(
+ readme_settings_async("sidebar_management_settings", "base_settings")
+ )
+ self.base_settings_comboBox.currentIndexChanged.connect(
+ lambda: update_settings(
+ "sidebar_management_settings",
+ "base_settings",
+ self.base_settings_comboBox.currentIndex(),
+ )
+ )
# 名单管理下拉框
self.name_management_comboBox = ComboBox(self)
- self.name_management_comboBox.addItems(get_content_combo_name_async("sidebar_management_settings", "name_management"))
- self.name_management_comboBox.setCurrentIndex(readme_settings_async("sidebar_management_settings", "name_management"))
- self.name_management_comboBox.currentIndexChanged.connect(lambda: update_settings("sidebar_management_settings", "name_management", self.name_management_comboBox.currentIndex()))
+ self.name_management_comboBox.addItems(
+ get_content_combo_name_async(
+ "sidebar_management_settings", "name_management"
+ )
+ )
+ self.name_management_comboBox.setCurrentIndex(
+ readme_settings_async("sidebar_management_settings", "name_management")
+ )
+ self.name_management_comboBox.currentIndexChanged.connect(
+ lambda: update_settings(
+ "sidebar_management_settings",
+ "name_management",
+ self.name_management_comboBox.currentIndex(),
+ )
+ )
# 抽取设置下拉框
self.draw_settings_comboBox = ComboBox(self)
- self.draw_settings_comboBox.addItems(get_content_combo_name_async("sidebar_management_settings", "draw_settings"))
- self.draw_settings_comboBox.setCurrentIndex(readme_settings_async("sidebar_management_settings", "draw_settings"))
- self.draw_settings_comboBox.currentIndexChanged.connect(lambda: update_settings("sidebar_management_settings", "draw_settings", self.draw_settings_comboBox.currentIndex()))
+ self.draw_settings_comboBox.addItems(
+ get_content_combo_name_async("sidebar_management_settings", "draw_settings")
+ )
+ self.draw_settings_comboBox.setCurrentIndex(
+ readme_settings_async("sidebar_management_settings", "draw_settings")
+ )
+ self.draw_settings_comboBox.currentIndexChanged.connect(
+ lambda: update_settings(
+ "sidebar_management_settings",
+ "draw_settings",
+ self.draw_settings_comboBox.currentIndex(),
+ )
+ )
# 通知服务下拉框
self.notification_service_comboBox = ComboBox(self)
- self.notification_service_comboBox.addItems(get_content_combo_name_async("sidebar_management_settings", "notification_service"))
- self.notification_service_comboBox.setCurrentIndex(readme_settings_async("sidebar_management_settings", "notification_service"))
- self.notification_service_comboBox.currentIndexChanged.connect(lambda: update_settings("sidebar_management_settings", "notification_service", self.notification_service_comboBox.currentIndex()))
+ self.notification_service_comboBox.addItems(
+ get_content_combo_name_async(
+ "sidebar_management_settings", "notification_service"
+ )
+ )
+ self.notification_service_comboBox.setCurrentIndex(
+ readme_settings_async("sidebar_management_settings", "notification_service")
+ )
+ self.notification_service_comboBox.currentIndexChanged.connect(
+ lambda: update_settings(
+ "sidebar_management_settings",
+ "notification_service",
+ self.notification_service_comboBox.currentIndex(),
+ )
+ )
# 安全设置下拉框
self.security_settings_comboBox = ComboBox(self)
- self.security_settings_comboBox.addItems(get_content_combo_name_async("sidebar_management_settings", "security_settings"))
- self.security_settings_comboBox.setCurrentIndex(readme_settings_async("sidebar_management_settings", "security_settings"))
- self.security_settings_comboBox.currentIndexChanged.connect(lambda: update_settings("sidebar_management_settings", "security_settings", self.security_settings_comboBox.currentIndex()))
+ self.security_settings_comboBox.addItems(
+ get_content_combo_name_async(
+ "sidebar_management_settings", "security_settings"
+ )
+ )
+ self.security_settings_comboBox.setCurrentIndex(
+ readme_settings_async("sidebar_management_settings", "security_settings")
+ )
+ self.security_settings_comboBox.currentIndexChanged.connect(
+ lambda: update_settings(
+ "sidebar_management_settings",
+ "security_settings",
+ self.security_settings_comboBox.currentIndex(),
+ )
+ )
# 个性设置下拉框
self.personal_settings_comboBox = ComboBox(self)
- self.personal_settings_comboBox.addItems(get_content_combo_name_async("sidebar_management_settings", "personal_settings"))
- self.personal_settings_comboBox.setCurrentIndex(readme_settings_async("sidebar_management_settings", "personal_settings"))
- self.personal_settings_comboBox.currentIndexChanged.connect(lambda: update_settings("sidebar_management_settings", "personal_settings", self.personal_settings_comboBox.currentIndex()))
+ self.personal_settings_comboBox.addItems(
+ get_content_combo_name_async(
+ "sidebar_management_settings", "personal_settings"
+ )
+ )
+ self.personal_settings_comboBox.setCurrentIndex(
+ readme_settings_async("sidebar_management_settings", "personal_settings")
+ )
+ self.personal_settings_comboBox.currentIndexChanged.connect(
+ lambda: update_settings(
+ "sidebar_management_settings",
+ "personal_settings",
+ self.personal_settings_comboBox.currentIndex(),
+ )
+ )
# 语音设置下拉框
self.voice_settings_comboBox = ComboBox(self)
- self.voice_settings_comboBox.addItems(get_content_combo_name_async("sidebar_management_settings", "voice_settings"))
- self.voice_settings_comboBox.setCurrentIndex(readme_settings_async("sidebar_management_settings", "voice_settings"))
- self.voice_settings_comboBox.currentIndexChanged.connect(lambda: update_settings("sidebar_management_settings", "voice_settings", self.voice_settings_comboBox.currentIndex()))
+ self.voice_settings_comboBox.addItems(
+ get_content_combo_name_async(
+ "sidebar_management_settings", "voice_settings"
+ )
+ )
+ self.voice_settings_comboBox.setCurrentIndex(
+ readme_settings_async("sidebar_management_settings", "voice_settings")
+ )
+ self.voice_settings_comboBox.currentIndexChanged.connect(
+ lambda: update_settings(
+ "sidebar_management_settings",
+ "voice_settings",
+ self.voice_settings_comboBox.currentIndex(),
+ )
+ )
# 设置界面历史记录下拉框
self.settings_history_comboBox = ComboBox(self)
- self.settings_history_comboBox.addItems(get_content_combo_name_async("sidebar_management_settings", "settings_history"))
- self.settings_history_comboBox.setCurrentIndex(readme_settings_async("sidebar_management_settings", "settings_history"))
- self.settings_history_comboBox.currentIndexChanged.connect(lambda: update_settings("sidebar_management_settings", "settings_history", self.settings_history_comboBox.currentIndex()))
+ self.settings_history_comboBox.addItems(
+ get_content_combo_name_async(
+ "sidebar_management_settings", "settings_history"
+ )
+ )
+ self.settings_history_comboBox.setCurrentIndex(
+ readme_settings_async("sidebar_management_settings", "settings_history")
+ )
+ self.settings_history_comboBox.currentIndexChanged.connect(
+ lambda: update_settings(
+ "sidebar_management_settings",
+ "settings_history",
+ self.settings_history_comboBox.currentIndex(),
+ )
+ )
# 更多设置下拉框
self.more_settings_comboBox = ComboBox(self)
- self.more_settings_comboBox.addItems(get_content_combo_name_async("sidebar_management_settings", "more_settings"))
- self.more_settings_comboBox.setCurrentIndex(readme_settings_async("sidebar_management_settings", "more_settings"))
- self.more_settings_comboBox.currentIndexChanged.connect(lambda: update_settings("sidebar_management_settings", "more_settings", self.more_settings_comboBox.currentIndex()))
+ self.more_settings_comboBox.addItems(
+ get_content_combo_name_async("sidebar_management_settings", "more_settings")
+ )
+ self.more_settings_comboBox.setCurrentIndex(
+ readme_settings_async("sidebar_management_settings", "more_settings")
+ )
+ self.more_settings_comboBox.currentIndexChanged.connect(
+ lambda: update_settings(
+ "sidebar_management_settings",
+ "more_settings",
+ self.more_settings_comboBox.currentIndex(),
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_home_20_filled"),
- get_content_name_async("sidebar_management_settings", "home"), get_content_description_async("sidebar_management_settings", "home"), self.home_comboBox)
- self.addGroup(get_theme_icon("ic_fluent_wrench_settings_20_filled"),
- get_content_name_async("sidebar_management_settings", "base_settings"), get_content_description_async("sidebar_management_settings", "base_settings"), self.base_settings_comboBox)
- self.addGroup(get_theme_icon("ic_fluent_list_20_filled"),
- get_content_name_async("sidebar_management_settings", "name_management"), get_content_description_async("sidebar_management_settings", "name_management"), self.name_management_comboBox)
- self.addGroup(get_theme_icon("ic_fluent_archive_20_filled"),
- get_content_name_async("sidebar_management_settings", "draw_settings"), get_content_description_async("sidebar_management_settings", "draw_settings"), self.draw_settings_comboBox)
- self.addGroup(get_theme_icon("ic_fluent_service_bell_20_filled"),
- get_content_name_async("sidebar_management_settings", "notification_service"), get_content_description_async("sidebar_management_settings", "notification_service"), self.notification_service_comboBox)
- self.addGroup(get_theme_icon("ic_fluent_shield_20_filled"),
- get_content_name_async("sidebar_management_settings", "security_settings"), get_content_description_async("sidebar_management_settings", "security_settings"), self.security_settings_comboBox)
- self.addGroup(get_theme_icon("ic_fluent_person_edit_20_filled"),
- get_content_name_async("sidebar_management_settings", "personal_settings"), get_content_description_async("sidebar_management_settings", "personal_settings"), self.personal_settings_comboBox)
- self.addGroup(get_theme_icon("ic_fluent_person_voice_20_filled"),
- get_content_name_async("sidebar_management_settings", "voice_settings"), get_content_description_async("sidebar_management_settings", "voice_settings"), self.voice_settings_comboBox)
- self.addGroup(get_theme_icon("ic_fluent_chat_history_20_filled"),
- get_content_name_async("sidebar_management_settings", "settings_history"), get_content_description_async("sidebar_management_settings", "settings_history"), self.settings_history_comboBox)
- self.addGroup(get_theme_icon("ic_fluent_more_horizontal_20_filled"),
- get_content_name_async("sidebar_management_settings", "more_settings"), get_content_description_async("sidebar_management_settings", "more_settings"), self.more_settings_comboBox)
+ self.addGroup(
+ get_theme_icon("ic_fluent_home_20_filled"),
+ get_content_name_async("sidebar_management_settings", "home"),
+ get_content_description_async("sidebar_management_settings", "home"),
+ self.home_comboBox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_wrench_settings_20_filled"),
+ get_content_name_async("sidebar_management_settings", "base_settings"),
+ get_content_description_async(
+ "sidebar_management_settings", "base_settings"
+ ),
+ self.base_settings_comboBox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_list_20_filled"),
+ get_content_name_async("sidebar_management_settings", "name_management"),
+ get_content_description_async(
+ "sidebar_management_settings", "name_management"
+ ),
+ self.name_management_comboBox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_archive_20_filled"),
+ get_content_name_async("sidebar_management_settings", "draw_settings"),
+ get_content_description_async(
+ "sidebar_management_settings", "draw_settings"
+ ),
+ self.draw_settings_comboBox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_service_bell_20_filled"),
+ get_content_name_async(
+ "sidebar_management_settings", "notification_service"
+ ),
+ get_content_description_async(
+ "sidebar_management_settings", "notification_service"
+ ),
+ self.notification_service_comboBox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_shield_20_filled"),
+ get_content_name_async("sidebar_management_settings", "security_settings"),
+ get_content_description_async(
+ "sidebar_management_settings", "security_settings"
+ ),
+ self.security_settings_comboBox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_person_edit_20_filled"),
+ get_content_name_async("sidebar_management_settings", "personal_settings"),
+ get_content_description_async(
+ "sidebar_management_settings", "personal_settings"
+ ),
+ self.personal_settings_comboBox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_person_voice_20_filled"),
+ get_content_name_async("sidebar_management_settings", "voice_settings"),
+ get_content_description_async(
+ "sidebar_management_settings", "voice_settings"
+ ),
+ self.voice_settings_comboBox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_chat_history_20_filled"),
+ get_content_name_async("sidebar_management_settings", "settings_history"),
+ get_content_description_async(
+ "sidebar_management_settings", "settings_history"
+ ),
+ self.settings_history_comboBox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_more_horizontal_20_filled"),
+ get_content_name_async("sidebar_management_settings", "more_settings"),
+ get_content_description_async(
+ "sidebar_management_settings", "more_settings"
+ ),
+ self.more_settings_comboBox,
+ )
+
class tray_management(GroupHeaderCardWidget):
def __init__(self, parent=None):
@@ -187,47 +454,133 @@ def __init__(self, parent=None):
# 暂时显示/隐藏主界面 按钮开关
self.show_hide_main_window_switch = SwitchButton(self)
- self.show_hide_main_window_switch.setOffText(get_content_switchbutton_name_async("tray_management", "show_hide_main_window", "disable"))
- self.show_hide_main_window_switch.setOnText(get_content_switchbutton_name_async("tray_management", "show_hide_main_window", "enable"))
- self.show_hide_main_window_switch.setChecked(readme_settings_async("tray_management", "show_hide_main_window"))
- self.show_hide_main_window_switch.checkedChanged.connect(lambda: update_settings("tray_management", "show_hide_main_window", self.show_hide_main_window_switch.isChecked()))
+ self.show_hide_main_window_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "tray_management", "show_hide_main_window", "disable"
+ )
+ )
+ self.show_hide_main_window_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "tray_management", "show_hide_main_window", "enable"
+ )
+ )
+ self.show_hide_main_window_switch.setChecked(
+ readme_settings_async("tray_management", "show_hide_main_window")
+ )
+ self.show_hide_main_window_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "tray_management",
+ "show_hide_main_window",
+ self.show_hide_main_window_switch.isChecked(),
+ )
+ )
# 打开设置界面 按钮开关
self.open_settings_switch = SwitchButton(self)
- self.open_settings_switch.setOffText(get_content_switchbutton_name_async("tray_management", "open_settings", "disable"))
- self.open_settings_switch.setOnText(get_content_switchbutton_name_async("tray_management", "open_settings", "enable"))
- self.open_settings_switch.setChecked(readme_settings_async("tray_management", "open_settings"))
- self.open_settings_switch.checkedChanged.connect(lambda: update_settings("tray_management", "open_settings", self.open_settings_switch.isChecked()))
+ self.open_settings_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "tray_management", "open_settings", "disable"
+ )
+ )
+ self.open_settings_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "tray_management", "open_settings", "enable"
+ )
+ )
+ self.open_settings_switch.setChecked(
+ readme_settings_async("tray_management", "open_settings")
+ )
+ self.open_settings_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "tray_management",
+ "open_settings",
+ self.open_settings_switch.isChecked(),
+ )
+ )
# 暂时显示/隐藏浮窗 按钮开关
- self.show_hide_float_window_switch = SwitchButton(self)
- self.show_hide_float_window_switch.setOffText(get_content_switchbutton_name_async("tray_management", "show_hide_float_window", "disable"))
- self.show_hide_float_window_switch.setOnText(get_content_switchbutton_name_async("tray_management", "show_hide_float_window", "enable"))
- self.show_hide_float_window_switch.setChecked(readme_settings_async("tray_management", "show_hide_float_window"))
- self.show_hide_float_window_switch.checkedChanged.connect(lambda: update_settings("tray_management", "show_hide_float_window", self.show_hide_float_window_switch.isChecked()))
+ self.show_hide_float_window_switch = SwitchButton(self)
+ self.show_hide_float_window_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "tray_management", "show_hide_float_window", "disable"
+ )
+ )
+ self.show_hide_float_window_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "tray_management", "show_hide_float_window", "enable"
+ )
+ )
+ self.show_hide_float_window_switch.setChecked(
+ readme_settings_async("tray_management", "show_hide_float_window")
+ )
+ self.show_hide_float_window_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "tray_management",
+ "show_hide_float_window",
+ self.show_hide_float_window_switch.isChecked(),
+ )
+ )
# 重启 按钮开关
self.restart_switch = SwitchButton(self)
- self.restart_switch.setOffText(get_content_switchbutton_name_async("tray_management", "restart", "disable"))
- self.restart_switch.setOnText(get_content_switchbutton_name_async("tray_management", "restart", "enable"))
- self.restart_switch.setChecked(readme_settings_async("tray_management", "restart"))
- self.restart_switch.checkedChanged.connect(lambda: update_settings("tray_management", "restart", self.restart_switch.isChecked()))
+ self.restart_switch.setOffText(
+ get_content_switchbutton_name_async("tray_management", "restart", "disable")
+ )
+ self.restart_switch.setOnText(
+ get_content_switchbutton_name_async("tray_management", "restart", "enable")
+ )
+ self.restart_switch.setChecked(
+ readme_settings_async("tray_management", "restart")
+ )
+ self.restart_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "tray_management", "restart", self.restart_switch.isChecked()
+ )
+ )
# 退出 按钮开关
self.exit_switch = SwitchButton(self)
- self.exit_switch.setOffText(get_content_switchbutton_name_async("tray_management", "exit", "disable"))
- self.exit_switch.setOnText(get_content_switchbutton_name_async("tray_management", "exit", "enable"))
+ self.exit_switch.setOffText(
+ get_content_switchbutton_name_async("tray_management", "exit", "disable")
+ )
+ self.exit_switch.setOnText(
+ get_content_switchbutton_name_async("tray_management", "exit", "enable")
+ )
self.exit_switch.setChecked(readme_settings_async("tray_management", "exit"))
- self.exit_switch.checkedChanged.connect(lambda: update_settings("tray_management", "exit", self.exit_switch.isChecked()))
+ self.exit_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "tray_management", "exit", self.exit_switch.isChecked()
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_window_text_20_filled"),
- get_content_name_async("tray_management", "show_hide_main_window"), get_content_description_async("tray_management", "show_hide_main_window"), self.show_hide_main_window_switch)
- self.addGroup(get_theme_icon("ic_fluent_settings_20_filled"),
- get_content_name_async("tray_management", "open_settings"), get_content_description_async("tray_management", "open_settings"), self.open_settings_switch)
- self.addGroup(get_theme_icon("ic_fluent_window_ad_20_filled"),
- get_content_name_async("tray_management", "show_hide_float_window"), get_content_description_async("tray_management", "show_hide_float_window"), self.show_hide_float_window_switch)
- self.addGroup(get_theme_icon("ic_fluent_arrow_reset_20_filled"),
- get_content_name_async("tray_management", "restart"), get_content_description_async("tray_management", "restart"), self.restart_switch)
- self.addGroup(get_theme_icon("ic_fluent_arrow_exit_20_filled"),
- get_content_name_async("tray_management", "exit"), get_content_description_async("tray_management", "exit"), self.exit_switch)
+ self.addGroup(
+ get_theme_icon("ic_fluent_window_text_20_filled"),
+ get_content_name_async("tray_management", "show_hide_main_window"),
+ get_content_description_async("tray_management", "show_hide_main_window"),
+ self.show_hide_main_window_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_settings_20_filled"),
+ get_content_name_async("tray_management", "open_settings"),
+ get_content_description_async("tray_management", "open_settings"),
+ self.open_settings_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_window_ad_20_filled"),
+ get_content_name_async("tray_management", "show_hide_float_window"),
+ get_content_description_async("tray_management", "show_hide_float_window"),
+ self.show_hide_float_window_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_arrow_reset_20_filled"),
+ get_content_name_async("tray_management", "restart"),
+ get_content_description_async("tray_management", "restart"),
+ self.restart_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_arrow_exit_20_filled"),
+ get_content_name_async("tray_management", "exit"),
+ get_content_description_async("tray_management", "exit"),
+ self.exit_switch,
+ )
diff --git a/app/view/settings/extraction_settings/__init__.py b/app/view/settings/extraction_settings/__init__.py
new file mode 100644
index 00000000..ec1b897b
--- /dev/null
+++ b/app/view/settings/extraction_settings/__init__.py
@@ -0,0 +1 @@
+"""Extraction settings pages."""
diff --git a/app/view/settings/extraction_settings/custom_draw_settings.py b/app/view/settings/extraction_settings/custom_draw_settings.py
index 711c9b7f..99747a96 100644
--- a/app/view/settings/extraction_settings/custom_draw_settings.py
+++ b/app/view/settings/extraction_settings/custom_draw_settings.py
@@ -1,16 +1,10 @@
# ==================================================
# 导入库
# ==================================================
-import json
-import os
-import sys
-import subprocess
-
-from loguru import logger
-from PyQt6.QtWidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from PySide6.QtNetwork import *
from qfluentwidgets import *
from app.tools.variable import *
@@ -20,6 +14,7 @@
from app.tools.settings_access import *
from app.Language.obtain_language import *
+
# ==================================================
# 自定义抽设置
# ==================================================
@@ -31,11 +26,27 @@ def __init__(self, parent=None):
# 开机自启设置
self.autostart_switch = SwitchButton()
- self.autostart_switch.setOffText(get_content_switchbutton_name_async("basic_settings", "autostart", "disable"))
- self.autostart_switch.setOnText(get_content_switchbutton_name_async("basic_settings", "autostart", "enable"))
- self.autostart_switch.setChecked(readme_settings_async("basic_settings", "autostart"))
- self.autostart_switch.checkedChanged.connect(lambda: update_settings("basic_settings", "autostart", self.autostart_switch.isChecked()))
+ self.autostart_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "basic_settings", "autostart", "disable"
+ )
+ )
+ self.autostart_switch.setOnText(
+ get_content_switchbutton_name_async("basic_settings", "autostart", "enable")
+ )
+ self.autostart_switch.setChecked(
+ readme_settings_async("basic_settings", "autostart")
+ )
+ self.autostart_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "basic_settings", "autostart", self.autostart_switch.isChecked()
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_arrow_sync_20_filled"),
- get_content_name_async("basic_settings", "autostart"), get_content_description_async("basic_settings", "autostart"), self.autostart_switch)
+ self.addGroup(
+ get_theme_icon("ic_fluent_arrow_sync_20_filled"),
+ get_content_name_async("basic_settings", "autostart"),
+ get_content_description_async("basic_settings", "autostart"),
+ self.autostart_switch,
+ )
diff --git a/app/view/settings/extraction_settings/instant_draw_settings.py b/app/view/settings/extraction_settings/instant_draw_settings.py
index 33a82f28..5c5b33f9 100644
--- a/app/view/settings/extraction_settings/instant_draw_settings.py
+++ b/app/view/settings/extraction_settings/instant_draw_settings.py
@@ -1,16 +1,13 @@
# ==================================================
# 导入库
# ==================================================
-import json
import os
-import sys
-import subprocess
from loguru import logger
-from PyQt6.QtWidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from PySide6.QtNetwork import *
from qfluentwidgets import *
from app.tools.variable import *
@@ -20,6 +17,7 @@
from app.tools.settings_access import *
from app.Language.obtain_language import *
+
# ==================================================
# 即抽设置
# ==================================================
@@ -47,100 +45,201 @@ def __init__(self, parent=None):
class instant_draw_extraction_function(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("instant_draw_settings", "extraction_function"))
+ self.setTitle(
+ get_content_name_async("instant_draw_settings", "extraction_function")
+ )
self.setBorderRadius(8)
- # 抽取模式下拉框
+ # 抽取模式下拉框(先创建,不在构造中填充)
self.draw_mode_combo = ComboBox()
- self.draw_mode_combo.addItems(get_content_combo_name_async("instant_draw_settings", "draw_mode"))
- self.draw_mode_combo.setCurrentIndex(readme_settings_async("instant_draw_settings", "draw_mode"))
self.draw_mode_combo.currentIndexChanged.connect(self.on_draw_mode_changed)
- # 清除抽取记录方式下拉框
+ # 清除抽取记录方式下拉框(延迟填充)
self.clear_record_combo = ComboBox()
- self.clear_record_combo.addItems(get_content_combo_name_async("instant_draw_settings", "clear_record"))
- self.clear_record_combo.setCurrentIndex(readme_settings_async("instant_draw_settings", "clear_record"))
- self.clear_record_combo.currentIndexChanged.connect(lambda: update_settings("instant_draw_settings", "clear_record", self.clear_record_combo.currentIndex()))
+ self.clear_record_combo.currentIndexChanged.connect(
+ lambda: update_settings(
+ "instant_draw_settings",
+ "clear_record",
+ self.clear_record_combo.currentIndex(),
+ )
+ )
# 半重复抽取次数输入框
self.half_repeat_spin = SpinBox()
self.half_repeat_spin.setFixedWidth(WIDTH_SPINBOX)
self.half_repeat_spin.setRange(0, 100)
self.half_repeat_spin.setSuffix("次")
- self.half_repeat_spin.setValue(readme_settings_async("instant_draw_settings", "half_repeat"))
- self.half_repeat_spin.valueChanged.connect(lambda: update_settings("instant_draw_settings", "half_repeat", self.half_repeat_spin.value()))
-
- # 抽取后定时清除时间输入框
- self.clear_time_spin = SpinBox()
- self.clear_time_spin.setFixedWidth(WIDTH_SPINBOX)
- self.clear_time_spin.setRange(0, 25600)
- self.clear_time_spin.setSuffix("秒")
- self.clear_time_spin.setValue(readme_settings_async("instant_draw_settings", "clear_time"))
- self.clear_time_spin.valueChanged.connect(lambda: update_settings("instant_draw_settings", "clear_time", self.clear_time_spin.value()))
-
- # 抽取方式下拉框
+ self.half_repeat_spin.setValue(
+ readme_settings_async("instant_draw_settings", "half_repeat")
+ )
+ self.half_repeat_spin.valueChanged.connect(
+ lambda: update_settings(
+ "instant_draw_settings", "half_repeat", self.half_repeat_spin.value()
+ )
+ )
+
+ # 抽取方式下拉框(延迟填充)
self.draw_type_combo = ComboBox()
- self.draw_type_combo.addItems(get_content_combo_name_async("instant_draw_settings", "draw_type"))
- self.draw_type_combo.setCurrentIndex(readme_settings_async("instant_draw_settings", "draw_type"))
- self.draw_type_combo.currentIndexChanged.connect(lambda: update_settings("instant_draw_settings", "draw_type", self.draw_type_combo.currentIndex()))
+ self.draw_type_combo.currentIndexChanged.connect(
+ lambda: update_settings(
+ "instant_draw_settings",
+ "draw_type",
+ self.draw_type_combo.currentIndex(),
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_document_bullet_list_cube_20_filled"),
- get_content_name_async("instant_draw_settings", "draw_mode"), get_content_description_async("instant_draw_settings", "draw_mode"), self.draw_mode_combo)
- self.addGroup(get_theme_icon("ic_fluent_text_clear_formatting_20_filled"),
- get_content_name_async("instant_draw_settings", "clear_record"), get_content_description_async("instant_draw_settings", "clear_record"), self.clear_record_combo)
- self.addGroup(get_theme_icon("ic_fluent_clipboard_bullet_list_20_filled"),
- get_content_name_async("instant_draw_settings", "half_repeat"), get_content_description_async("instant_draw_settings", "half_repeat"), self.half_repeat_spin)
- self.addGroup(get_theme_icon("ic_fluent_timer_off_20_filled"),
- get_content_name_async("instant_draw_settings", "clear_time"), get_content_description_async("instant_draw_settings", "clear_time"), self.clear_time_spin)
- self.addGroup(get_theme_icon("ic_fluent_drawer_add_20_filled"),
- get_content_name_async("instant_draw_settings", "draw_type"), get_content_description_async("instant_draw_settings", "draw_type"), self.draw_type_combo)
-
- # 初始化时调用一次,确保界面状态与设置一致
- self.on_draw_mode_changed()
+ self.addGroup(
+ get_theme_icon("ic_fluent_document_bullet_list_cube_20_filled"),
+ get_content_name_async("instant_draw_settings", "draw_mode"),
+ get_content_description_async("instant_draw_settings", "draw_mode"),
+ self.draw_mode_combo,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_text_clear_formatting_20_filled"),
+ get_content_name_async("instant_draw_settings", "clear_record"),
+ get_content_description_async("instant_draw_settings", "clear_record"),
+ self.clear_record_combo,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_clipboard_bullet_list_20_filled"),
+ get_content_name_async("instant_draw_settings", "half_repeat"),
+ get_content_description_async("instant_draw_settings", "half_repeat"),
+ self.half_repeat_spin,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_drawer_add_20_filled"),
+ get_content_name_async("instant_draw_settings", "draw_type"),
+ get_content_description_async("instant_draw_settings", "draw_type"),
+ self.draw_type_combo,
+ )
+
+ # 初始化时先启动后台加载选项并在加载完成后触发 on_draw_mode_changed
+ QTimer.singleShot(0, self._start_background_load)
+
+ def _start_background_load(self):
+ """在后台线程加载所有需要的选项与初始值,然后回填UI"""
+
+ class _Signals(QObject):
+ loaded = Signal(dict)
+
+ class _Loader(QRunnable):
+ def __init__(self, fn, signals):
+ super().__init__()
+ self.fn = fn
+ self.signals = signals
+
+ def run(self):
+ try:
+ data = self.fn()
+ self.signals.loaded.emit(data)
+ except Exception as e:
+ logger.error(f"后台加载 instant_draw_settings 数据失败: {e}")
+
+ def _collect():
+ data = {}
+ try:
+ data["draw_mode_items"] = get_content_combo_name_async(
+ "instant_draw_settings", "draw_mode"
+ )
+ data["draw_mode_index"] = readme_settings_async(
+ "instant_draw_settings", "draw_mode"
+ )
+ data["clear_record_items"] = get_content_combo_name_async(
+ "instant_draw_settings", "clear_record"
+ )
+ data["clear_record_index"] = readme_settings_async(
+ "instant_draw_settings", "clear_record"
+ )
+ data["half_repeat_value"] = readme_settings_async(
+ "instant_draw_settings", "half_repeat"
+ )
+ data["draw_type_items"] = get_content_combo_name_async(
+ "instant_draw_settings", "draw_type"
+ )
+ data["draw_type_index"] = readme_settings_async(
+ "instant_draw_settings", "draw_type"
+ )
+ except Exception as e:
+ logger.error(f"收集 instant_draw_settings 初始数据失败: {e}")
+ return data
+
+ signals = _Signals()
+ signals.loaded.connect(self._on_background_loaded)
+ runnable = _Loader(_collect, signals)
+ QThreadPool.globalInstance().start(runnable)
+
+ def _on_background_loaded(self, data: dict):
+ try:
+ if "draw_mode_items" in data:
+ self.draw_mode_combo.addItems(data.get("draw_mode_items", []))
+ self.draw_mode_combo.setCurrentIndex(data.get("draw_mode_index", 0))
+ if "clear_record_items" in data:
+ self.clear_record_combo.addItems(data.get("clear_record_items", []))
+ self.clear_record_combo.setCurrentIndex(
+ data.get("clear_record_index", 0)
+ )
+ if "half_repeat_value" in data:
+ self.half_repeat_spin.setValue(data.get("half_repeat_value", 0))
+ if "draw_type_items" in data:
+ self.draw_type_combo.addItems(data.get("draw_type_items", []))
+ self.draw_type_combo.setCurrentIndex(data.get("draw_type_index", 0))
+
+ # 数据填充后再触发一次模式更新以保证 UI 状态一致
+ self.on_draw_mode_changed()
+ except Exception as e:
+ logger.error(f"回填 instant_draw_settings 数据失败: {e}")
def on_draw_mode_changed(self):
"""当抽取模式改变时的处理逻辑"""
# 更新设置值
- update_settings("instant_draw_settings", "draw_mode", self.draw_mode_combo.currentIndex())
-
+ update_settings(
+ "instant_draw_settings", "draw_mode", self.draw_mode_combo.currentIndex()
+ )
+
# 获取当前抽取模式索引
draw_mode_index = self.draw_mode_combo.currentIndex()
-
+
# 根据抽取模式设置不同的控制逻辑
if draw_mode_index == 0: # 重复抽取模式
# 禁用清除抽取记录方式下拉框
self.clear_record_combo.setEnabled(False)
# 清空当前选项
self.clear_record_combo.clear()
- self.clear_record_combo.addItems(get_any_position_value_async("instant_draw_settings", "clear_record", "combo_items_other"))
+ self.clear_record_combo.addItems(
+ get_any_position_value_async(
+ "instant_draw_settings", "clear_record", "combo_items_other"
+ )
+ )
# 强制设置为"无需清除"(索引2)
self.clear_record_combo.setCurrentIndex(2)
# 更新设置
update_settings("instant_draw_settings", "clear_record", 2)
-
+
# 设置half_repeat_spin为0并禁用
self.half_repeat_spin.setEnabled(False)
self.half_repeat_spin.setRange(0, 0)
self.half_repeat_spin.setValue(0)
# 更新设置
update_settings("instant_draw_settings", "half_repeat", 0)
-
+
else: # 不重复抽取模式或半重复抽取模式
# 启用清除抽取记录方式下拉框
self.clear_record_combo.setEnabled(True)
-
+
# 清空当前选项
self.clear_record_combo.clear()
-
+
# 添加前两个选项(不包含"无需清除")
- self.clear_record_combo.addItems(get_content_combo_name_async("instant_draw_settings", "clear_record"))
-
+ self.clear_record_combo.addItems(
+ get_content_combo_name_async("instant_draw_settings", "clear_record")
+ )
+
# 设置默认选择第一个选项
self.clear_record_combo.setCurrentIndex(0)
# 更新设置
update_settings("instant_draw_settings", "clear_record", 0)
-
+
# 根据具体模式设置half_repeat_spin
if draw_mode_index == 1: # 不重复抽取模式
# 设置half_repeat_spin为1并禁用
@@ -163,7 +262,9 @@ def on_draw_mode_changed(self):
class instant_draw_display_settings(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("instant_draw_settings", "display_settings"))
+ self.setTitle(
+ get_content_name_async("instant_draw_settings", "display_settings")
+ )
self.setBorderRadius(8)
# 字体大小输入框
@@ -171,28 +272,66 @@ def __init__(self, parent=None):
self.font_size_spin.setFixedWidth(WIDTH_SPINBOX)
self.font_size_spin.setRange(10, 1000)
self.font_size_spin.setSuffix("px")
- self.font_size_spin.setValue(readme_settings_async("instant_draw_settings", "font_size"))
- self.font_size_spin.valueChanged.connect(lambda: update_settings("instant_draw_settings", "font_size", self.font_size_spin.value()))
+ self.font_size_spin.setValue(
+ readme_settings_async("instant_draw_settings", "font_size")
+ )
+ self.font_size_spin.valueChanged.connect(
+ lambda: update_settings(
+ "instant_draw_settings", "font_size", self.font_size_spin.value()
+ )
+ )
# RESULT格式下拉框
self.display_format_combo = ComboBox()
- self.display_format_combo.addItems(get_content_combo_name_async("instant_draw_settings", "display_format"))
- self.display_format_combo.setCurrentIndex(readme_settings_async("instant_draw_settings", "display_format"))
- self.display_format_combo.currentIndexChanged.connect(lambda: update_settings("instant_draw_settings", "display_format", self.display_format_combo.currentIndex()))
+ self.display_format_combo.addItems(
+ get_content_combo_name_async("instant_draw_settings", "display_format")
+ )
+ self.display_format_combo.setCurrentIndex(
+ readme_settings_async("instant_draw_settings", "display_format")
+ )
+ self.display_format_combo.currentIndexChanged.connect(
+ lambda: update_settings(
+ "instant_draw_settings",
+ "display_format",
+ self.display_format_combo.currentIndex(),
+ )
+ )
# 显示随机组员格式下拉框
self.show_random_format_combo = ComboBox()
- self.show_random_format_combo.addItems(get_content_combo_name_async("instant_draw_settings", "show_random"))
- self.show_random_format_combo.setCurrentIndex(readme_settings_async("instant_draw_settings", "show_random"))
- self.show_random_format_combo.currentIndexChanged.connect(lambda: update_settings("instant_draw_settings", "show_random", self.show_random_format_combo.currentIndex()))
+ self.show_random_format_combo.addItems(
+ get_content_combo_name_async("instant_draw_settings", "show_random")
+ )
+ self.show_random_format_combo.setCurrentIndex(
+ readme_settings_async("instant_draw_settings", "show_random")
+ )
+ self.show_random_format_combo.currentIndexChanged.connect(
+ lambda: update_settings(
+ "instant_draw_settings",
+ "show_random",
+ self.show_random_format_combo.currentIndex(),
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_text_font_20_filled"),
- get_content_name_async("instant_draw_settings", "font_size"), get_content_description_async("instant_draw_settings", "font_size"), self.font_size_spin)
- self.addGroup(get_theme_icon("ic_fluent_slide_text_sparkle_20_filled"),
- get_content_name_async("instant_draw_settings", "display_format"), get_content_description_async("instant_draw_settings", "display_format"), self.display_format_combo)
- self.addGroup(get_theme_icon("ic_fluent_group_list_20_filled"),
- get_content_name_async("instant_draw_settings", "show_random"), get_content_description_async("instant_draw_settings", "show_random"), self.show_random_format_combo)
+ self.addGroup(
+ get_theme_icon("ic_fluent_text_font_20_filled"),
+ get_content_name_async("instant_draw_settings", "font_size"),
+ get_content_description_async("instant_draw_settings", "font_size"),
+ self.font_size_spin,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_slide_text_sparkle_20_filled"),
+ get_content_name_async("instant_draw_settings", "display_format"),
+ get_content_description_async("instant_draw_settings", "display_format"),
+ self.display_format_combo,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_group_list_20_filled"),
+ get_content_name_async("instant_draw_settings", "show_random"),
+ get_content_description_async("instant_draw_settings", "show_random"),
+ self.show_random_format_combo,
+ )
class instant_draw_animation_settings(QWidget):
@@ -215,120 +354,257 @@ def __init__(self, parent=None):
self.student_image_widget = instant_draw_student_image_settings(self)
self.vBoxLayout.addWidget(self.student_image_widget)
- # 添加音乐设置组件
- self.music_settings_widget = instant_draw_music_settings(self)
- self.vBoxLayout.addWidget(self.music_settings_widget)
-
class instant_draw_basic_animation_settings(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("instant_draw_settings", "basic_animation_settings"))
+ self.setTitle(
+ get_content_name_async("instant_draw_settings", "basic_animation_settings")
+ )
self.setBorderRadius(8)
# 动画模式下拉框
self.animation_combo = ComboBox()
- self.animation_combo.addItems(get_content_combo_name_async("instant_draw_settings", "animation"))
- self.animation_combo.setCurrentIndex(readme_settings_async("instant_draw_settings", "animation"))
- self.animation_combo.currentIndexChanged.connect(lambda: update_settings("instant_draw_settings", "animation", self.animation_combo.currentIndex()))
+ self.animation_combo.addItems(
+ get_content_combo_name_async("instant_draw_settings", "animation")
+ )
+ self.animation_combo.setCurrentIndex(
+ readme_settings_async("instant_draw_settings", "animation")
+ )
+ self.animation_combo.currentIndexChanged.connect(
+ lambda: update_settings(
+ "instant_draw_settings",
+ "animation",
+ self.animation_combo.currentIndex(),
+ )
+ )
# 动画间隔输入框
self.animation_interval_spin = SpinBox()
self.animation_interval_spin.setFixedWidth(WIDTH_SPINBOX)
self.animation_interval_spin.setRange(1, 2000)
self.animation_interval_spin.setSuffix("ms")
- self.animation_interval_spin.setValue(readme_settings_async("instant_draw_settings", "animation_interval"))
- self.animation_interval_spin.valueChanged.connect(lambda: update_settings("instant_draw_settings", "animation_interval", self.animation_interval_spin.value()))
+ self.animation_interval_spin.setValue(
+ readme_settings_async("instant_draw_settings", "animation_interval")
+ )
+ self.animation_interval_spin.valueChanged.connect(
+ lambda: update_settings(
+ "instant_draw_settings",
+ "animation_interval",
+ self.animation_interval_spin.value(),
+ )
+ )
# 自动播放次数输入框
self.autoplay_count_spin = SpinBox()
self.autoplay_count_spin.setFixedWidth(WIDTH_SPINBOX)
self.autoplay_count_spin.setRange(0, 100)
self.autoplay_count_spin.setSuffix("次")
- self.autoplay_count_spin.setValue(readme_settings_async("instant_draw_settings", "autoplay_count"))
- self.autoplay_count_spin.valueChanged.connect(lambda: update_settings("instant_draw_settings", "autoplay_count", self.autoplay_count_spin.value()))
+ self.autoplay_count_spin.setValue(
+ readme_settings_async("instant_draw_settings", "autoplay_count")
+ )
+ self.autoplay_count_spin.valueChanged.connect(
+ lambda: update_settings(
+ "instant_draw_settings",
+ "autoplay_count",
+ self.autoplay_count_spin.value(),
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_sanitize_20_filled"),
- get_content_name_async("instant_draw_settings", "animation"), get_content_description_async("instant_draw_settings", "animation"), self.animation_combo)
- self.addGroup(get_theme_icon("ic_fluent_timeline_20_filled"),
- get_content_name_async("instant_draw_settings", "animation_interval"), get_content_description_async("instant_draw_settings", "animation_interval"), self.animation_interval_spin)
- self.addGroup(get_theme_icon("ic_fluent_slide_play_20_filled"),
- get_content_name_async("instant_draw_settings", "autoplay_count"), get_content_description_async("instant_draw_settings", "autoplay_count"), self.autoplay_count_spin)
+ self.addGroup(
+ get_theme_icon("ic_fluent_sanitize_20_filled"),
+ get_content_name_async("instant_draw_settings", "animation"),
+ get_content_description_async("instant_draw_settings", "animation"),
+ self.animation_combo,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_timeline_20_filled"),
+ get_content_name_async("instant_draw_settings", "animation_interval"),
+ get_content_description_async(
+ "instant_draw_settings", "animation_interval"
+ ),
+ self.animation_interval_spin,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_slide_play_20_filled"),
+ get_content_name_async("instant_draw_settings", "autoplay_count"),
+ get_content_description_async("instant_draw_settings", "autoplay_count"),
+ self.autoplay_count_spin,
+ )
class instant_draw_color_theme_settings(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("instant_draw_settings", "color_theme_settings"))
+ self.setTitle(
+ get_content_name_async("instant_draw_settings", "color_theme_settings")
+ )
self.setBorderRadius(8)
# 动画颜色主题下拉框
self.animation_color_theme_combo = ComboBox()
- self.animation_color_theme_combo.addItems(get_content_combo_name_async("instant_draw_settings", "animation_color_theme"))
- self.animation_color_theme_combo.setCurrentIndex(readme_settings_async("instant_draw_settings", "animation_color_theme"))
- self.animation_color_theme_combo.currentIndexChanged.connect(lambda: update_settings("instant_draw_settings", "animation_color_theme", self.animation_color_theme_combo.currentIndex()))
+ self.animation_color_theme_combo.addItems(
+ get_content_combo_name_async(
+ "instant_draw_settings", "animation_color_theme"
+ )
+ )
+ self.animation_color_theme_combo.setCurrentIndex(
+ readme_settings_async("instant_draw_settings", "animation_color_theme")
+ )
+ self.animation_color_theme_combo.currentIndexChanged.connect(
+ lambda: update_settings(
+ "instant_draw_settings",
+ "animation_color_theme",
+ self.animation_color_theme_combo.currentIndex(),
+ )
+ )
# 结果颜色主题下拉框
self.result_color_theme_combo = ComboBox()
- self.result_color_theme_combo.addItems(get_content_combo_name_async("instant_draw_settings", "result_color_theme"))
- self.result_color_theme_combo.setCurrentIndex(readme_settings_async("instant_draw_settings", "result_color_theme"))
- self.result_color_theme_combo.currentIndexChanged.connect(lambda: update_settings("instant_draw_settings", "result_color_theme", self.result_color_theme_combo.currentIndex()))
+ self.result_color_theme_combo.addItems(
+ get_content_combo_name_async("instant_draw_settings", "result_color_theme")
+ )
+ self.result_color_theme_combo.setCurrentIndex(
+ readme_settings_async("instant_draw_settings", "result_color_theme")
+ )
+ self.result_color_theme_combo.currentIndexChanged.connect(
+ lambda: update_settings(
+ "instant_draw_settings",
+ "result_color_theme",
+ self.result_color_theme_combo.currentIndex(),
+ )
+ )
# 动画固定颜色
- self.animation_fixed_color_button = ColorConfigItem("Theme", "Color", readme_settings_async("instant_draw_settings", "animation_fixed_color"))
- self.animation_fixed_color_button.valueChanged.connect(lambda color: update_settings("instant_draw_settings", "animation_fixed_color", color.name()))
-
+ self.animation_fixed_color_button = ColorConfigItem(
+ "Theme",
+ "Color",
+ readme_settings_async("instant_draw_settings", "animation_fixed_color"),
+ )
+ self.animation_fixed_color_button.valueChanged.connect(
+ lambda color: update_settings(
+ "instant_draw_settings", "animation_fixed_color", color.name()
+ )
+ )
+
# 结果固定颜色
- self.result_fixed_color_button = ColorConfigItem("Theme", "Color", readme_settings_async("instant_draw_settings", "result_fixed_color"))
- self.result_fixed_color_button.valueChanged.connect(lambda color: update_settings("instant_draw_settings", "result_fixed_color", color.name()))
+ self.result_fixed_color_button = ColorConfigItem(
+ "Theme",
+ "Color",
+ readme_settings_async("instant_draw_settings", "result_fixed_color"),
+ )
+ self.result_fixed_color_button.valueChanged.connect(
+ lambda color: update_settings(
+ "instant_draw_settings", "result_fixed_color", color.name()
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_color_20_filled"),
- get_content_name_async("instant_draw_settings", "animation_color_theme"), get_content_description_async("instant_draw_settings", "animation_color_theme"), self.animation_color_theme_combo)
- self.addGroup(get_theme_icon("ic_fluent_color_20_filled"),
- get_content_name_async("instant_draw_settings", "result_color_theme"), get_content_description_async("instant_draw_settings", "result_color_theme"), self.result_color_theme_combo)
+ self.addGroup(
+ get_theme_icon("ic_fluent_color_20_filled"),
+ get_content_name_async("instant_draw_settings", "animation_color_theme"),
+ get_content_description_async(
+ "instant_draw_settings", "animation_color_theme"
+ ),
+ self.animation_color_theme_combo,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_color_20_filled"),
+ get_content_name_async("instant_draw_settings", "result_color_theme"),
+ get_content_description_async(
+ "instant_draw_settings", "result_color_theme"
+ ),
+ self.result_color_theme_combo,
+ )
self.animationColorCard = ColorSettingCard(
self.animation_fixed_color_button,
get_theme_icon("ic_fluent_text_color_20_filled"),
- self.tr(get_content_name_async("instant_draw_settings", "animation_fixed_color")),
- self.tr(get_content_description_async("instant_draw_settings", "animation_fixed_color")),
- self
+ self.tr(
+ get_content_name_async("instant_draw_settings", "animation_fixed_color")
+ ),
+ self.tr(
+ get_content_description_async(
+ "instant_draw_settings", "animation_fixed_color"
+ )
+ ),
+ self,
)
self.resultColorCard = ColorSettingCard(
self.result_fixed_color_button,
get_theme_icon("ic_fluent_text_color_20_filled"),
- self.tr(get_content_name_async("instant_draw_settings", "result_fixed_color")),
- self.tr(get_content_description_async("instant_draw_settings", "result_fixed_color")),
- self
+ self.tr(
+ get_content_name_async("instant_draw_settings", "result_fixed_color")
+ ),
+ self.tr(
+ get_content_description_async(
+ "instant_draw_settings", "result_fixed_color"
+ )
+ ),
+ self,
)
self.vBoxLayout.addWidget(self.animationColorCard)
self.vBoxLayout.addWidget(self.resultColorCard)
+
class instant_draw_student_image_settings(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("instant_draw_settings", "student_image_settings"))
+ self.setTitle(
+ get_content_name_async("instant_draw_settings", "student_image_settings")
+ )
self.setBorderRadius(8)
# 学生图片开关
self.student_image_switch = SwitchButton()
- self.student_image_switch.setOffText(get_content_switchbutton_name_async("instant_draw_settings", "student_image", "disable"))
- self.student_image_switch.setOnText(get_content_switchbutton_name_async("instant_draw_settings", "student_image", "enable"))
- self.student_image_switch.setChecked(readme_settings_async("instant_draw_settings", "student_image"))
- self.student_image_switch.checkedChanged.connect(lambda: update_settings("instant_draw_settings", "student_image", self.student_image_switch.isChecked()))
+ self.student_image_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "instant_draw_settings", "student_image", "disable"
+ )
+ )
+ self.student_image_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "instant_draw_settings", "student_image", "enable"
+ )
+ )
+ self.student_image_switch.setChecked(
+ readme_settings_async("instant_draw_settings", "student_image")
+ )
+ self.student_image_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "instant_draw_settings",
+ "student_image",
+ self.student_image_switch.isChecked(),
+ )
+ )
# 打开学生图片文件夹按钮
- self.open_student_image_folder_button = PushButton(get_content_name_async("instant_draw_settings", "open_student_image_folder"))
- self.open_student_image_folder_button.clicked.connect(lambda: self.open_student_image_folder())
+ self.open_student_image_folder_button = PushButton(
+ get_content_name_async("instant_draw_settings", "open_student_image_folder")
+ )
+ self.open_student_image_folder_button.clicked.connect(
+ lambda: self.open_student_image_folder()
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_image_circle_20_filled"),
- get_content_name_async("instant_draw_settings", "student_image"), get_content_description_async("instant_draw_settings", "student_image"), self.student_image_switch)
- self.addGroup(get_theme_icon("ic_fluent_folder_open_20_filled"),
- get_content_name_async("instant_draw_settings", "open_student_image_folder"), get_content_description_async("instant_draw_settings", "open_student_image_folder"), self.open_student_image_folder_button)
+ self.addGroup(
+ get_theme_icon("ic_fluent_image_circle_20_filled"),
+ get_content_name_async("instant_draw_settings", "student_image"),
+ get_content_description_async("instant_draw_settings", "student_image"),
+ self.student_image_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_folder_open_20_filled"),
+ get_content_name_async(
+ "instant_draw_settings", "open_student_image_folder"
+ ),
+ get_content_description_async(
+ "instant_draw_settings", "open_student_image_folder"
+ ),
+ self.open_student_image_folder_button,
+ )
def open_student_image_folder(self):
"""打开学生图片文件夹"""
@@ -339,121 +615,3 @@ def open_student_image_folder(self):
QDesktopServices.openUrl(QUrl.fromLocalFile(str(folder_path)))
else:
logger.error("无法获取学生图片文件夹路径")
-
-class instant_draw_music_settings(GroupHeaderCardWidget):
- def __init__(self, parent=None):
- super().__init__(parent)
- self.setTitle(get_content_name_async("instant_draw_settings", "music_settings"))
- self.setBorderRadius(8)
-
- # 动画音乐开关
- self.animation_music_switch = SwitchButton()
- self.animation_music_switch.setOffText(get_content_switchbutton_name_async("instant_draw_settings", "animation_music", "disable"))
- self.animation_music_switch.setOnText(get_content_switchbutton_name_async("instant_draw_settings", "animation_music", "enable"))
- self.animation_music_switch.setChecked(readme_settings_async("instant_draw_settings", "animation_music"))
- self.animation_music_switch.checkedChanged.connect(lambda: update_settings("instant_draw_settings", "animation_music", self.animation_music_switch.isChecked()))
-
- # 结果音乐开关
- self.result_music_switch = SwitchButton()
- self.result_music_switch.setOffText(get_content_switchbutton_name_async("instant_draw_settings", "result_music", "disable"))
- self.result_music_switch.setOnText(get_content_switchbutton_name_async("instant_draw_settings", "result_music", "enable"))
- self.result_music_switch.setChecked(readme_settings_async("instant_draw_settings", "result_music"))
- self.result_music_switch.checkedChanged.connect(lambda: update_settings("instant_draw_settings", "result_music", self.result_music_switch.isChecked()))
-
- # 动画音乐文件夹按钮
- self.open_animation_music_folder_button = PushButton(get_content_name_async("instant_draw_settings", "open_animation_music_folder"))
- self.open_animation_music_folder_button.clicked.connect(lambda: self.open_animation_music_folder())
-
- # 结果音乐文件夹按钮
- self.open_result_music_folder_button = PushButton(get_content_name_async("instant_draw_settings", "open_result_music_folder"))
- self.open_result_music_folder_button.clicked.connect(lambda: self.open_result_music_folder())
-
- # 动画音乐音量
- self.animation_music_volume_spin = SpinBox()
- self.animation_music_volume_spin.setFixedWidth(WIDTH_SPINBOX)
- self.animation_music_volume_spin.setRange(0, 100)
- self.animation_music_volume_spin.setSuffix("%")
- self.animation_music_volume_spin.setValue(readme_settings_async("instant_draw_settings", "animation_music_volume"))
- self.animation_music_volume_spin.valueChanged.connect(lambda: update_settings("instant_draw_settings", "animation_music_volume", self.animation_music_volume_spin.value()))
-
- # 结果音乐音量
- self.result_music_volume_spin = SpinBox()
- self.result_music_volume_spin.setFixedWidth(WIDTH_SPINBOX)
- self.result_music_volume_spin.setRange(0, 100)
- self.result_music_volume_spin.setSuffix("%")
- self.result_music_volume_spin.setValue(readme_settings_async("instant_draw_settings", "result_music_volume"))
- self.result_music_volume_spin.valueChanged.connect(lambda: update_settings("instant_draw_settings", "result_music_volume", self.result_music_volume_spin.value()))
-
- # 动画音乐淡入时间
- self.animation_music_fade_in_spin = SpinBox()
- self.animation_music_fade_in_spin.setFixedWidth(WIDTH_SPINBOX)
- self.animation_music_fade_in_spin.setRange(0, 1000)
- self.animation_music_fade_in_spin.setSuffix("ms")
- self.animation_music_fade_in_spin.setValue(readme_settings_async("instant_draw_settings", "animation_music_fade_in"))
- self.animation_music_fade_in_spin.valueChanged.connect(lambda: update_settings("instant_draw_settings", "animation_music_fade_in", self.animation_music_fade_in_spin.value()))
-
- # 结果音乐淡入时间
- self.result_music_fade_in_spin = SpinBox()
- self.result_music_fade_in_spin.setFixedWidth(WIDTH_SPINBOX)
- self.result_music_fade_in_spin.setRange(0, 1000)
- self.result_music_fade_in_spin.setSuffix("ms")
- self.result_music_fade_in_spin.setValue(readme_settings_async("instant_draw_settings", "result_music_fade_in"))
- self.result_music_fade_in_spin.valueChanged.connect(lambda: update_settings("instant_draw_settings", "result_music_fade_in", self.result_music_fade_in_spin.value()))
-
- # 动画音乐淡出时间
- self.animation_music_fade_out_spin = SpinBox()
- self.animation_music_fade_out_spin.setFixedWidth(WIDTH_SPINBOX)
- self.animation_music_fade_out_spin.setRange(0, 1000)
- self.animation_music_fade_out_spin.setSuffix("ms")
- self.animation_music_fade_out_spin.setValue(readme_settings_async("instant_draw_settings", "animation_music_fade_out"))
- self.animation_music_fade_out_spin.valueChanged.connect(lambda: update_settings("instant_draw_settings", "animation_music_fade_out", self.animation_music_fade_out_spin.value()))
-
- # 结果音乐淡出时间
- self.result_music_fade_out_spin = SpinBox()
- self.result_music_fade_out_spin.setFixedWidth(WIDTH_SPINBOX)
- self.result_music_fade_out_spin.setRange(0, 1000)
- self.result_music_fade_out_spin.setSuffix("ms")
- self.result_music_fade_out_spin.setValue(readme_settings_async("instant_draw_settings", "result_music_fade_out"))
- self.result_music_fade_out_spin.valueChanged.connect(lambda: update_settings("instant_draw_settings", "result_music_fade_out", self.result_music_fade_out_spin.value()))
-
- # 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_music_note_2_20_filled"),
- get_content_name_async("instant_draw_settings", "animation_music"), get_content_description_async("instant_draw_settings", "animation_music"), self.animation_music_switch)
- self.addGroup(get_theme_icon("ic_fluent_music_note_2_20_filled"),
- get_content_name_async("instant_draw_settings", "result_music"), get_content_description_async("instant_draw_settings", "result_music"), self.result_music_switch)
- self.addGroup(get_theme_icon("ic_fluent_folder_open_20_filled"),
- get_content_name_async("instant_draw_settings", "open_animation_music_folder"), get_content_description_async("instant_draw_settings", "open_animation_music_folder"), self.open_animation_music_folder_button)
- self.addGroup(get_theme_icon("ic_fluent_folder_open_20_filled"),
- get_content_name_async("instant_draw_settings", "open_result_music_folder"), get_content_description_async("instant_draw_settings", "open_result_music_folder"), self.open_result_music_folder_button)
- self.addGroup(get_theme_icon("ic_fluent_speaker_2_20_filled"),
- get_content_name_async("instant_draw_settings", "animation_music_volume"), get_content_description_async("instant_draw_settings", "animation_music_volume"), self.animation_music_volume_spin)
- self.addGroup(get_theme_icon("ic_fluent_speaker_2_20_filled"),
- get_content_name_async("instant_draw_settings", "result_music_volume"), get_content_description_async("instant_draw_settings", "result_music_volume"), self.result_music_volume_spin)
- self.addGroup(get_theme_icon("ic_fluent_arrow_up_20_filled"),
- get_content_name_async("instant_draw_settings", "animation_music_fade_in"), get_content_description_async("instant_draw_settings", "animation_music_fade_in"), self.animation_music_fade_in_spin)
- self.addGroup(get_theme_icon("ic_fluent_arrow_up_20_filled"),
- get_content_name_async("instant_draw_settings", "result_music_fade_in"), get_content_description_async("instant_draw_settings", "result_music_fade_in"), self.result_music_fade_in_spin)
- self.addGroup(get_theme_icon("ic_fluent_arrow_down_20_filled"),
- get_content_name_async("instant_draw_settings", "animation_music_fade_out"), get_content_description_async("instant_draw_settings", "animation_music_fade_out"), self.animation_music_fade_out_spin)
- self.addGroup(get_theme_icon("ic_fluent_arrow_down_20_filled"),
- get_content_name_async("instant_draw_settings", "result_music_fade_out"), get_content_description_async("instant_draw_settings", "result_music_fade_out"), self.result_music_fade_out_spin)
-
- def open_animation_music_folder(self):
- """打开动画音乐文件夹"""
- folder_path = get_resources_path(ANIMATION_MUSIC_FOLDER)
- if not folder_path.exists():
- os.makedirs(folder_path)
- if folder_path:
- QDesktopServices.openUrl(QUrl.fromLocalFile(str(folder_path)))
- else:
- logger.error("无法获取动画音乐文件夹路径")
-
- def open_result_music_folder(self):
- """打开结果音乐文件夹"""
- folder_path = get_resources_path(RESULT_MUSIC_FOLDER)
- if not folder_path.exists():
- os.makedirs(folder_path)
- if folder_path:
- QDesktopServices.openUrl(QUrl.fromLocalFile(str(folder_path)))
- else:
- logger.error("无法获取结果音乐文件夹路径")
\ No newline at end of file
diff --git a/app/view/settings/extraction_settings/lottery_settings.py b/app/view/settings/extraction_settings/lottery_settings.py
index d1d41be8..bea35e15 100644
--- a/app/view/settings/extraction_settings/lottery_settings.py
+++ b/app/view/settings/extraction_settings/lottery_settings.py
@@ -1,16 +1,13 @@
# ==================================================
# 导入库
# ==================================================
-import json
import os
-import sys
-import subprocess
from loguru import logger
-from PyQt6.QtWidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from PySide6.QtNetwork import *
from qfluentwidgets import *
from app.tools.variable import *
@@ -20,6 +17,7 @@
from app.tools.settings_access import *
from app.Language.obtain_language import *
+
# ==================================================
# 抽奖设置
# ==================================================
@@ -52,95 +50,187 @@ def __init__(self, parent=None):
# 抽取模式下拉框
self.draw_mode_combo = ComboBox()
- self.draw_mode_combo.addItems(get_content_combo_name_async("lottery_settings", "draw_mode"))
- self.draw_mode_combo.setCurrentIndex(readme_settings_async("lottery_settings", "draw_mode"))
self.draw_mode_combo.currentIndexChanged.connect(self.on_draw_mode_changed)
- # 清除抽取记录方式下拉框
self.clear_record_combo = ComboBox()
- self.clear_record_combo.addItems(get_content_combo_name_async("lottery_settings", "clear_record"))
- self.clear_record_combo.setCurrentIndex(readme_settings_async("lottery_settings", "clear_record"))
- self.clear_record_combo.currentIndexChanged.connect(lambda: update_settings("lottery_settings", "clear_record", self.clear_record_combo.currentIndex()))
+ self.clear_record_combo.currentIndexChanged.connect(
+ lambda: update_settings(
+ "lottery_settings",
+ "clear_record",
+ self.clear_record_combo.currentIndex(),
+ )
+ )
# 半重复抽取次数输入框
self.half_repeat_spin = SpinBox()
self.half_repeat_spin.setFixedWidth(WIDTH_SPINBOX)
self.half_repeat_spin.setRange(0, 100)
self.half_repeat_spin.setSuffix("次")
- self.half_repeat_spin.setValue(readme_settings_async("lottery_settings", "half_repeat"))
- self.half_repeat_spin.valueChanged.connect(lambda: update_settings("lottery_settings", "half_repeat", self.half_repeat_spin.value()))
-
- # 抽取后定时清除时间输入框
- self.clear_time_spin = SpinBox()
- self.clear_time_spin.setFixedWidth(WIDTH_SPINBOX)
- self.clear_time_spin.setRange(0, 25600)
- self.clear_time_spin.setSuffix("秒")
- self.clear_time_spin.setValue(readme_settings_async("lottery_settings", "clear_time"))
- self.clear_time_spin.valueChanged.connect(lambda: update_settings("lottery_settings", "clear_time", self.clear_time_spin.value()))
-
- # 抽取方式下拉框
+ self.half_repeat_spin.setValue(
+ readme_settings_async("lottery_settings", "half_repeat")
+ )
+ self.half_repeat_spin.valueChanged.connect(
+ lambda: update_settings(
+ "lottery_settings", "half_repeat", self.half_repeat_spin.value()
+ )
+ )
+
self.draw_type_combo = ComboBox()
- self.draw_type_combo.addItems(get_content_combo_name_async("lottery_settings", "draw_type"))
- self.draw_type_combo.setCurrentIndex(readme_settings_async("lottery_settings", "draw_type"))
- self.draw_type_combo.currentIndexChanged.connect(lambda: update_settings("lottery_settings", "draw_type", self.draw_type_combo.currentIndex()))
+ self.draw_type_combo.currentIndexChanged.connect(
+ lambda: update_settings(
+ "lottery_settings", "draw_type", self.draw_type_combo.currentIndex()
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_document_bullet_list_cube_20_filled"),
- get_content_name_async("lottery_settings", "draw_mode"), get_content_description_async("lottery_settings", "draw_mode"), self.draw_mode_combo)
- self.addGroup(get_theme_icon("ic_fluent_text_clear_formatting_20_filled"),
- get_content_name_async("lottery_settings", "clear_record"), get_content_description_async("lottery_settings", "clear_record"), self.clear_record_combo)
- self.addGroup(get_theme_icon("ic_fluent_clipboard_bullet_list_20_filled"),
- get_content_name_async("lottery_settings", "half_repeat"), get_content_description_async("lottery_settings", "half_repeat"), self.half_repeat_spin)
- self.addGroup(get_theme_icon("ic_fluent_timer_off_20_filled"),
- get_content_name_async("lottery_settings", "clear_time"), get_content_description_async("lottery_settings", "clear_time"), self.clear_time_spin)
- self.addGroup(get_theme_icon("ic_fluent_drawer_add_20_filled"),
- get_content_name_async("lottery_settings", "draw_type"), get_content_description_async("lottery_settings", "draw_type"), self.draw_type_combo)
-
- # 初始化时调用一次,确保界面状态与设置一致
- self.on_draw_mode_changed()
+ self.addGroup(
+ get_theme_icon("ic_fluent_document_bullet_list_cube_20_filled"),
+ get_content_name_async("lottery_settings", "draw_mode"),
+ get_content_description_async("lottery_settings", "draw_mode"),
+ self.draw_mode_combo,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_text_clear_formatting_20_filled"),
+ get_content_name_async("lottery_settings", "clear_record"),
+ get_content_description_async("lottery_settings", "clear_record"),
+ self.clear_record_combo,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_clipboard_bullet_list_20_filled"),
+ get_content_name_async("lottery_settings", "half_repeat"),
+ get_content_description_async("lottery_settings", "half_repeat"),
+ self.half_repeat_spin,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_drawer_add_20_filled"),
+ get_content_name_async("lottery_settings", "draw_type"),
+ get_content_description_async("lottery_settings", "draw_type"),
+ self.draw_type_combo,
+ )
+
+ # 初始化时在后台加载选项并回填
+ QTimer.singleShot(0, self._start_background_load)
+
+ def _start_background_load(self):
+ class _Signals(QObject):
+ loaded = Signal(dict)
+
+ class _Loader(QRunnable):
+ def __init__(self, fn, signals):
+ super().__init__()
+ self.fn = fn
+ self.signals = signals
+
+ def run(self):
+ try:
+ data = self.fn()
+ self.signals.loaded.emit(data)
+ except Exception as e:
+ logger.error(f"后台加载 lottery_settings 数据失败: {e}")
+
+ def _collect():
+ data = {}
+ try:
+ data["draw_mode_items"] = get_content_combo_name_async(
+ "lottery_settings", "draw_mode"
+ )
+ data["draw_mode_index"] = readme_settings_async(
+ "lottery_settings", "draw_mode"
+ )
+ data["clear_record_items"] = get_content_combo_name_async(
+ "lottery_settings", "clear_record"
+ )
+ data["clear_record_index"] = readme_settings_async(
+ "lottery_settings", "clear_record"
+ )
+ data["half_repeat_value"] = readme_settings_async(
+ "lottery_settings", "half_repeat"
+ )
+ data["draw_type_items"] = get_content_combo_name_async(
+ "lottery_settings", "draw_type"
+ )
+ data["draw_type_index"] = readme_settings_async(
+ "lottery_settings", "draw_type"
+ )
+ except Exception as e:
+ logger.error(f"收集 lottery_settings 初始数据失败: {e}")
+ return data
+
+ signals = _Signals()
+ signals.loaded.connect(self._on_background_loaded)
+ runnable = _Loader(_collect, signals)
+ QThreadPool.globalInstance().start(runnable)
+
+ def _on_background_loaded(self, data: dict):
+ try:
+ if "draw_mode_items" in data:
+ self.draw_mode_combo.addItems(data.get("draw_mode_items", []))
+ self.draw_mode_combo.setCurrentIndex(data.get("draw_mode_index", 0))
+ if "clear_record_items" in data:
+ self.clear_record_combo.addItems(data.get("clear_record_items", []))
+ self.clear_record_combo.setCurrentIndex(
+ data.get("clear_record_index", 0)
+ )
+ if "half_repeat_value" in data:
+ self.half_repeat_spin.setValue(data.get("half_repeat_value", 0))
+ if "draw_type_items" in data:
+ self.draw_type_combo.addItems(data.get("draw_type_items", []))
+ self.draw_type_combo.setCurrentIndex(data.get("draw_type_index", 0))
+
+ self.on_draw_mode_changed()
+ except Exception as e:
+ logger.error(f"回填 lottery_settings 数据失败: {e}")
def on_draw_mode_changed(self):
"""当抽取模式改变时的处理逻辑"""
# 更新设置值
- update_settings("lottery_settings", "draw_mode", self.draw_mode_combo.currentIndex())
-
+ update_settings(
+ "lottery_settings", "draw_mode", self.draw_mode_combo.currentIndex()
+ )
+
# 获取当前抽取模式索引
draw_mode_index = self.draw_mode_combo.currentIndex()
-
+
# 根据抽取模式设置不同的控制逻辑
if draw_mode_index == 0: # 重复抽取模式
# 禁用清除抽取记录方式下拉框
self.clear_record_combo.setEnabled(False)
# 清空当前选项
self.clear_record_combo.clear()
- self.clear_record_combo.addItems(get_any_position_value_async("lottery_settings", "clear_record", "combo_items_other"))
+ self.clear_record_combo.addItems(
+ get_any_position_value_async(
+ "lottery_settings", "clear_record", "combo_items_other"
+ )
+ )
# 强制设置为"无需清除"(索引2)
self.clear_record_combo.setCurrentIndex(2)
# 更新设置
update_settings("lottery_settings", "clear_record", 2)
-
+
# 设置half_repeat_spin为0并禁用
self.half_repeat_spin.setEnabled(False)
self.half_repeat_spin.setRange(0, 0)
self.half_repeat_spin.setValue(0)
# 更新设置
update_settings("lottery_settings", "half_repeat", 0)
-
+
else: # 不重复抽取模式或半重复抽取模式
# 启用清除抽取记录方式下拉框
self.clear_record_combo.setEnabled(True)
-
+
# 清空当前选项
self.clear_record_combo.clear()
-
+
# 添加前两个选项(不包含"无需清除")
- self.clear_record_combo.addItems(get_content_combo_name_async("lottery_settings", "clear_record"))
-
+ self.clear_record_combo.addItems(
+ get_content_combo_name_async("lottery_settings", "clear_record")
+ )
+
# 设置默认选择第一个选项
self.clear_record_combo.setCurrentIndex(0)
# 更新设置
update_settings("lottery_settings", "clear_record", 0)
-
+
# 根据具体模式设置half_repeat_spin
if draw_mode_index == 1: # 不重复抽取模式
# 设置half_repeat_spin为1并禁用
@@ -171,20 +261,44 @@ def __init__(self, parent=None):
self.font_size_spin.setFixedWidth(WIDTH_SPINBOX)
self.font_size_spin.setRange(10, 1000)
self.font_size_spin.setSuffix("px")
- self.font_size_spin.setValue(readme_settings_async("lottery_settings", "font_size"))
- self.font_size_spin.valueChanged.connect(lambda: update_settings("lottery_settings", "font_size", self.font_size_spin.value()))
+ self.font_size_spin.setValue(
+ readme_settings_async("lottery_settings", "font_size")
+ )
+ self.font_size_spin.valueChanged.connect(
+ lambda: update_settings(
+ "lottery_settings", "font_size", self.font_size_spin.value()
+ )
+ )
# 结果显示格式下拉框
self.display_format_combo = ComboBox()
- self.display_format_combo.addItems(get_content_combo_name_async("lottery_settings", "display_format"))
- self.display_format_combo.setCurrentIndex(readme_settings_async("lottery_settings", "display_format"))
- self.display_format_combo.currentIndexChanged.connect(lambda: update_settings("lottery_settings", "display_format", self.display_format_combo.currentIndex()))
+ self.display_format_combo.addItems(
+ get_content_combo_name_async("lottery_settings", "display_format")
+ )
+ self.display_format_combo.setCurrentIndex(
+ readme_settings_async("lottery_settings", "display_format")
+ )
+ self.display_format_combo.currentIndexChanged.connect(
+ lambda: update_settings(
+ "lottery_settings",
+ "display_format",
+ self.display_format_combo.currentIndex(),
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_text_font_20_filled"),
- get_content_name_async("lottery_settings", "font_size"), get_content_description_async("lottery_settings", "font_size"), self.font_size_spin)
- self.addGroup(get_theme_icon("ic_fluent_slide_text_sparkle_20_filled"),
- get_content_name_async("lottery_settings", "display_format"), get_content_description_async("lottery_settings", "display_format"), self.display_format_combo)
+ self.addGroup(
+ get_theme_icon("ic_fluent_text_font_20_filled"),
+ get_content_name_async("lottery_settings", "font_size"),
+ get_content_description_async("lottery_settings", "font_size"),
+ self.font_size_spin,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_slide_text_sparkle_20_filled"),
+ get_content_name_async("lottery_settings", "display_format"),
+ get_content_description_async("lottery_settings", "display_format"),
+ self.display_format_combo,
+ )
class lottery_animation_settings(QWidget):
@@ -207,120 +321,239 @@ def __init__(self, parent=None):
self.lottery_image_widget = lottery_lottery_image_settings(self)
self.vBoxLayout.addWidget(self.lottery_image_widget)
- # 添加音乐设置组件
- self.music_settings_widget = lottery_music_settings(self)
- self.vBoxLayout.addWidget(self.music_settings_widget)
-
class lottery_basic_animation_settings(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("lottery_settings", "basic_animation_settings"))
+ self.setTitle(
+ get_content_name_async("lottery_settings", "basic_animation_settings")
+ )
self.setBorderRadius(8)
# 动画模式下拉框
self.animation_combo = ComboBox()
- self.animation_combo.addItems(get_content_combo_name_async("lottery_settings", "animation"))
- self.animation_combo.setCurrentIndex(readme_settings_async("lottery_settings", "animation"))
- self.animation_combo.currentIndexChanged.connect(lambda: update_settings("lottery_settings", "animation", self.animation_combo.currentIndex()))
+ self.animation_combo.addItems(
+ get_content_combo_name_async("lottery_settings", "animation")
+ )
+ self.animation_combo.setCurrentIndex(
+ readme_settings_async("lottery_settings", "animation")
+ )
+ self.animation_combo.currentIndexChanged.connect(
+ lambda: update_settings(
+ "lottery_settings", "animation", self.animation_combo.currentIndex()
+ )
+ )
# 动画间隔输入框
self.animation_interval_spin = SpinBox()
self.animation_interval_spin.setFixedWidth(WIDTH_SPINBOX)
self.animation_interval_spin.setRange(1, 2000)
self.animation_interval_spin.setSuffix("ms")
- self.animation_interval_spin.setValue(readme_settings_async("lottery_settings", "animation_interval"))
- self.animation_interval_spin.valueChanged.connect(lambda: update_settings("lottery_settings", "animation_interval", self.animation_interval_spin.value()))
+ self.animation_interval_spin.setValue(
+ readme_settings_async("lottery_settings", "animation_interval")
+ )
+ self.animation_interval_spin.valueChanged.connect(
+ lambda: update_settings(
+ "lottery_settings",
+ "animation_interval",
+ self.animation_interval_spin.value(),
+ )
+ )
# 自动播放次数输入框
self.autoplay_count_spin = SpinBox()
self.autoplay_count_spin.setFixedWidth(WIDTH_SPINBOX)
self.autoplay_count_spin.setRange(0, 100)
self.autoplay_count_spin.setSuffix("次")
- self.autoplay_count_spin.setValue(readme_settings_async("lottery_settings", "autoplay_count"))
- self.autoplay_count_spin.valueChanged.connect(lambda: update_settings("lottery_settings", "autoplay_count", self.autoplay_count_spin.value()))
+ self.autoplay_count_spin.setValue(
+ readme_settings_async("lottery_settings", "autoplay_count")
+ )
+ self.autoplay_count_spin.valueChanged.connect(
+ lambda: update_settings(
+ "lottery_settings", "autoplay_count", self.autoplay_count_spin.value()
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_sanitize_20_filled"),
- get_content_name_async("lottery_settings", "animation"), get_content_description_async("lottery_settings", "animation"), self.animation_combo)
- self.addGroup(get_theme_icon("ic_fluent_timeline_20_filled"),
- get_content_name_async("lottery_settings", "animation_interval"), get_content_description_async("lottery_settings", "animation_interval"), self.animation_interval_spin)
- self.addGroup(get_theme_icon("ic_fluent_slide_play_20_filled"),
- get_content_name_async("lottery_settings", "autoplay_count"), get_content_description_async("lottery_settings", "autoplay_count"), self.autoplay_count_spin)
+ self.addGroup(
+ get_theme_icon("ic_fluent_sanitize_20_filled"),
+ get_content_name_async("lottery_settings", "animation"),
+ get_content_description_async("lottery_settings", "animation"),
+ self.animation_combo,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_timeline_20_filled"),
+ get_content_name_async("lottery_settings", "animation_interval"),
+ get_content_description_async("lottery_settings", "animation_interval"),
+ self.animation_interval_spin,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_slide_play_20_filled"),
+ get_content_name_async("lottery_settings", "autoplay_count"),
+ get_content_description_async("lottery_settings", "autoplay_count"),
+ self.autoplay_count_spin,
+ )
class lottery_color_theme_settings(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("lottery_settings", "color_theme_settings"))
+ self.setTitle(
+ get_content_name_async("lottery_settings", "color_theme_settings")
+ )
self.setBorderRadius(8)
# 动画颜色主题下拉框
self.animation_color_theme_combo = ComboBox()
- self.animation_color_theme_combo.addItems(get_content_combo_name_async("lottery_settings", "animation_color_theme"))
- self.animation_color_theme_combo.setCurrentIndex(readme_settings_async("lottery_settings", "animation_color_theme"))
- self.animation_color_theme_combo.currentIndexChanged.connect(lambda: update_settings("lottery_settings", "animation_color_theme", self.animation_color_theme_combo.currentIndex()))
+ self.animation_color_theme_combo.addItems(
+ get_content_combo_name_async("lottery_settings", "animation_color_theme")
+ )
+ self.animation_color_theme_combo.setCurrentIndex(
+ readme_settings_async("lottery_settings", "animation_color_theme")
+ )
+ self.animation_color_theme_combo.currentIndexChanged.connect(
+ lambda: update_settings(
+ "lottery_settings",
+ "animation_color_theme",
+ self.animation_color_theme_combo.currentIndex(),
+ )
+ )
# 结果颜色主题下拉框
self.result_color_theme_combo = ComboBox()
- self.result_color_theme_combo.addItems(get_content_combo_name_async("lottery_settings", "result_color_theme"))
- self.result_color_theme_combo.setCurrentIndex(readme_settings_async("lottery_settings", "result_color_theme"))
- self.result_color_theme_combo.currentIndexChanged.connect(lambda: update_settings("lottery_settings", "result_color_theme", self.result_color_theme_combo.currentIndex()))
+ self.result_color_theme_combo.addItems(
+ get_content_combo_name_async("lottery_settings", "result_color_theme")
+ )
+ self.result_color_theme_combo.setCurrentIndex(
+ readme_settings_async("lottery_settings", "result_color_theme")
+ )
+ self.result_color_theme_combo.currentIndexChanged.connect(
+ lambda: update_settings(
+ "lottery_settings",
+ "result_color_theme",
+ self.result_color_theme_combo.currentIndex(),
+ )
+ )
# 动画固定颜色
- self.animation_fixed_color_button = ColorConfigItem("Theme", "Color", readme_settings_async("lottery_settings", "animation_fixed_color"))
- self.animation_fixed_color_button.valueChanged.connect(lambda color: update_settings("lottery_settings", "animation_fixed_color", color.name()))
-
+ self.animation_fixed_color_button = ColorConfigItem(
+ "Theme",
+ "Color",
+ readme_settings_async("lottery_settings", "animation_fixed_color"),
+ )
+ self.animation_fixed_color_button.valueChanged.connect(
+ lambda color: update_settings(
+ "lottery_settings", "animation_fixed_color", color.name()
+ )
+ )
+
# 结果固定颜色
- self.result_fixed_color_button = ColorConfigItem("Theme", "Color", readme_settings_async("lottery_settings", "result_fixed_color"))
- self.result_fixed_color_button.valueChanged.connect(lambda color: update_settings("lottery_settings", "result_fixed_color", color.name()))
+ self.result_fixed_color_button = ColorConfigItem(
+ "Theme",
+ "Color",
+ readme_settings_async("lottery_settings", "result_fixed_color"),
+ )
+ self.result_fixed_color_button.valueChanged.connect(
+ lambda color: update_settings(
+ "lottery_settings", "result_fixed_color", color.name()
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_color_20_filled"),
- get_content_name_async("lottery_settings", "animation_color_theme"), get_content_description_async("lottery_settings", "animation_color_theme"), self.animation_color_theme_combo)
- self.addGroup(get_theme_icon("ic_fluent_color_20_filled"),
- get_content_name_async("lottery_settings", "result_color_theme"), get_content_description_async("lottery_settings", "result_color_theme"), self.result_color_theme_combo)
+ self.addGroup(
+ get_theme_icon("ic_fluent_color_20_filled"),
+ get_content_name_async("lottery_settings", "animation_color_theme"),
+ get_content_description_async("lottery_settings", "animation_color_theme"),
+ self.animation_color_theme_combo,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_color_20_filled"),
+ get_content_name_async("lottery_settings", "result_color_theme"),
+ get_content_description_async("lottery_settings", "result_color_theme"),
+ self.result_color_theme_combo,
+ )
self.animationColorCard = ColorSettingCard(
self.animation_fixed_color_button,
get_theme_icon("ic_fluent_text_color_20_filled"),
- self.tr(get_content_name_async("lottery_settings", "animation_fixed_color")),
- self.tr(get_content_description_async("lottery_settings", "animation_fixed_color")),
- self
+ self.tr(
+ get_content_name_async("lottery_settings", "animation_fixed_color")
+ ),
+ self.tr(
+ get_content_description_async(
+ "lottery_settings", "animation_fixed_color"
+ )
+ ),
+ self,
)
self.resultColorCard = ColorSettingCard(
self.result_fixed_color_button,
get_theme_icon("ic_fluent_text_color_20_filled"),
self.tr(get_content_name_async("lottery_settings", "result_fixed_color")),
- self.tr(get_content_description_async("lottery_settings", "result_fixed_color")),
- self
+ self.tr(
+ get_content_description_async("lottery_settings", "result_fixed_color")
+ ),
+ self,
)
self.vBoxLayout.addWidget(self.animationColorCard)
self.vBoxLayout.addWidget(self.resultColorCard)
+
class lottery_lottery_image_settings(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("lottery_settings", "lottery_image_settings"))
+ self.setTitle(
+ get_content_name_async("lottery_settings", "lottery_image_settings")
+ )
self.setBorderRadius(8)
# 奖品图片开关
self.lottery_image_switch = SwitchButton()
- self.lottery_image_switch.setOffText(get_content_switchbutton_name_async("lottery_settings", "lottery_image", "disable"))
- self.lottery_image_switch.setOnText(get_content_switchbutton_name_async("lottery_settings", "lottery_image", "enable"))
- self.lottery_image_switch.setChecked(readme_settings_async("lottery_settings", "lottery_image"))
- self.lottery_image_switch.checkedChanged.connect(lambda: update_settings("lottery_settings", "lottery_image", self.lottery_image_switch.isChecked()))
+ self.lottery_image_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "lottery_settings", "lottery_image", "disable"
+ )
+ )
+ self.lottery_image_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "lottery_settings", "lottery_image", "enable"
+ )
+ )
+ self.lottery_image_switch.setChecked(
+ readme_settings_async("lottery_settings", "lottery_image")
+ )
+ self.lottery_image_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "lottery_settings",
+ "lottery_image",
+ self.lottery_image_switch.isChecked(),
+ )
+ )
# 打开奖品图片文件夹按钮
- self.open_lottery_image_folder_button = PushButton(get_content_name_async("lottery_settings", "open_lottery_image_folder"))
- self.open_lottery_image_folder_button.clicked.connect(lambda: self.open_lottery_image_folder())
+ self.open_lottery_image_folder_button = PushButton(
+ get_content_name_async("lottery_settings", "open_lottery_image_folder")
+ )
+ self.open_lottery_image_folder_button.clicked.connect(
+ lambda: self.open_lottery_image_folder()
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_image_circle_20_filled"),
- get_content_name_async("lottery_settings", "lottery_image"), get_content_description_async("lottery_settings", "lottery_image"), self.lottery_image_switch)
- self.addGroup(get_theme_icon("ic_fluent_folder_open_20_filled"),
- get_content_name_async("lottery_settings", "open_lottery_image_folder"), get_content_description_async("lottery_settings", "open_lottery_image_folder"), self.open_lottery_image_folder_button)
+ self.addGroup(
+ get_theme_icon("ic_fluent_image_circle_20_filled"),
+ get_content_name_async("lottery_settings", "lottery_image"),
+ get_content_description_async("lottery_settings", "lottery_image"),
+ self.lottery_image_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_folder_open_20_filled"),
+ get_content_name_async("lottery_settings", "open_lottery_image_folder"),
+ get_content_description_async(
+ "lottery_settings", "open_lottery_image_folder"
+ ),
+ self.open_lottery_image_folder_button,
+ )
def open_lottery_image_folder(self):
"""打开奖品图片文件夹"""
@@ -331,121 +564,3 @@ def open_lottery_image_folder(self):
QDesktopServices.openUrl(QUrl.fromLocalFile(str(folder_path)))
else:
logger.error("无法获取奖品图片文件夹路径")
-
-class lottery_music_settings(GroupHeaderCardWidget):
- def __init__(self, parent=None):
- super().__init__(parent)
- self.setTitle(get_content_name_async("lottery_settings", "music_settings"))
- self.setBorderRadius(8)
-
- # 动画音乐开关
- self.animation_music_switch = SwitchButton()
- self.animation_music_switch.setOffText(get_content_switchbutton_name_async("lottery_settings", "animation_music", "disable"))
- self.animation_music_switch.setOnText(get_content_switchbutton_name_async("lottery_settings", "animation_music", "enable"))
- self.animation_music_switch.setChecked(readme_settings_async("lottery_settings", "animation_music"))
- self.animation_music_switch.checkedChanged.connect(lambda: update_settings("lottery_settings", "animation_music", self.animation_music_switch.isChecked()))
-
- # 结果音乐开关
- self.result_music_switch = SwitchButton()
- self.result_music_switch.setOffText(get_content_switchbutton_name_async("lottery_settings", "result_music", "disable"))
- self.result_music_switch.setOnText(get_content_switchbutton_name_async("lottery_settings", "result_music", "enable"))
- self.result_music_switch.setChecked(readme_settings_async("lottery_settings", "result_music"))
- self.result_music_switch.checkedChanged.connect(lambda: update_settings("lottery_settings", "result_music", self.result_music_switch.isChecked()))
-
- # 动画音乐文件夹按钮
- self.open_animation_music_folder_button = PushButton(get_content_name_async("lottery_settings", "open_animation_music_folder"))
- self.open_animation_music_folder_button.clicked.connect(lambda: self.open_animation_music_folder())
-
- # 结果音乐文件夹按钮
- self.open_result_music_folder_button = PushButton(get_content_name_async("lottery_settings", "open_result_music_folder"))
- self.open_result_music_folder_button.clicked.connect(lambda: self.open_result_music_folder())
-
- # 动画音乐音量
- self.animation_music_volume_spin = SpinBox()
- self.animation_music_volume_spin.setFixedWidth(WIDTH_SPINBOX)
- self.animation_music_volume_spin.setRange(0, 100)
- self.animation_music_volume_spin.setSuffix("%")
- self.animation_music_volume_spin.setValue(readme_settings_async("lottery_settings", "animation_music_volume"))
- self.animation_music_volume_spin.valueChanged.connect(lambda: update_settings("lottery_settings", "animation_music_volume", self.animation_music_volume_spin.value()))
-
- # 结果音乐音量
- self.result_music_volume_spin = SpinBox()
- self.result_music_volume_spin.setFixedWidth(WIDTH_SPINBOX)
- self.result_music_volume_spin.setRange(0, 100)
- self.result_music_volume_spin.setSuffix("%")
- self.result_music_volume_spin.setValue(readme_settings_async("lottery_settings", "result_music_volume"))
- self.result_music_volume_spin.valueChanged.connect(lambda: update_settings("lottery_settings", "result_music_volume", self.result_music_volume_spin.value()))
-
- # 动画音乐淡入时间
- self.animation_music_fade_in_spin = SpinBox()
- self.animation_music_fade_in_spin.setFixedWidth(WIDTH_SPINBOX)
- self.animation_music_fade_in_spin.setRange(0, 1000)
- self.animation_music_fade_in_spin.setSuffix("ms")
- self.animation_music_fade_in_spin.setValue(readme_settings_async("lottery_settings", "animation_music_fade_in"))
- self.animation_music_fade_in_spin.valueChanged.connect(lambda: update_settings("lottery_settings", "animation_music_fade_in", self.animation_music_fade_in_spin.value()))
-
- # 结果音乐淡入时间
- self.result_music_fade_in_spin = SpinBox()
- self.result_music_fade_in_spin.setFixedWidth(WIDTH_SPINBOX)
- self.result_music_fade_in_spin.setRange(0, 1000)
- self.result_music_fade_in_spin.setSuffix("ms")
- self.result_music_fade_in_spin.setValue(readme_settings_async("lottery_settings", "result_music_fade_in"))
- self.result_music_fade_in_spin.valueChanged.connect(lambda: update_settings("lottery_settings", "result_music_fade_in", self.result_music_fade_in_spin.value()))
-
- # 动画音乐淡出时间
- self.animation_music_fade_out_spin = SpinBox()
- self.animation_music_fade_out_spin.setFixedWidth(WIDTH_SPINBOX)
- self.animation_music_fade_out_spin.setRange(0, 1000)
- self.animation_music_fade_out_spin.setSuffix("ms")
- self.animation_music_fade_out_spin.setValue(readme_settings_async("lottery_settings", "animation_music_fade_out"))
- self.animation_music_fade_out_spin.valueChanged.connect(lambda: update_settings("lottery_settings", "animation_music_fade_out", self.animation_music_fade_out_spin.value()))
-
- # 结果音乐淡出时间
- self.result_music_fade_out_spin = SpinBox()
- self.result_music_fade_out_spin.setFixedWidth(WIDTH_SPINBOX)
- self.result_music_fade_out_spin.setRange(0, 1000)
- self.result_music_fade_out_spin.setSuffix("ms")
- self.result_music_fade_out_spin.setValue(readme_settings_async("lottery_settings", "result_music_fade_out"))
- self.result_music_fade_out_spin.valueChanged.connect(lambda: update_settings("lottery_settings", "result_music_fade_out", self.result_music_fade_out_spin.value()))
-
- # 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_music_note_2_20_filled"),
- get_content_name_async("lottery_settings", "animation_music"), get_content_description_async("lottery_settings", "animation_music"), self.animation_music_switch)
- self.addGroup(get_theme_icon("ic_fluent_music_note_2_20_filled"),
- get_content_name_async("lottery_settings", "result_music"), get_content_description_async("lottery_settings", "result_music"), self.result_music_switch)
- self.addGroup(get_theme_icon("ic_fluent_folder_open_20_filled"),
- get_content_name_async("lottery_settings", "open_animation_music_folder"), get_content_description_async("lottery_settings", "open_animation_music_folder"), self.open_animation_music_folder_button)
- self.addGroup(get_theme_icon("ic_fluent_folder_open_20_filled"),
- get_content_name_async("lottery_settings", "open_result_music_folder"), get_content_description_async("lottery_settings", "open_result_music_folder"), self.open_result_music_folder_button)
- self.addGroup(get_theme_icon("ic_fluent_speaker_2_20_filled"),
- get_content_name_async("lottery_settings", "animation_music_volume"), get_content_description_async("lottery_settings", "animation_music_volume"), self.animation_music_volume_spin)
- self.addGroup(get_theme_icon("ic_fluent_speaker_2_20_filled"),
- get_content_name_async("lottery_settings", "result_music_volume"), get_content_description_async("lottery_settings", "result_music_volume"), self.result_music_volume_spin)
- self.addGroup(get_theme_icon("ic_fluent_arrow_up_20_filled"),
- get_content_name_async("lottery_settings", "animation_music_fade_in"), get_content_description_async("lottery_settings", "animation_music_fade_in"), self.animation_music_fade_in_spin)
- self.addGroup(get_theme_icon("ic_fluent_arrow_up_20_filled"),
- get_content_name_async("lottery_settings", "result_music_fade_in"), get_content_description_async("lottery_settings", "result_music_fade_in"), self.result_music_fade_in_spin)
- self.addGroup(get_theme_icon("ic_fluent_arrow_down_20_filled"),
- get_content_name_async("lottery_settings", "animation_music_fade_out"), get_content_description_async("lottery_settings", "animation_music_fade_out"), self.animation_music_fade_out_spin)
- self.addGroup(get_theme_icon("ic_fluent_arrow_down_20_filled"),
- get_content_name_async("lottery_settings", "result_music_fade_out"), get_content_description_async("lottery_settings", "result_music_fade_out"), self.result_music_fade_out_spin)
-
- def open_animation_music_folder(self):
- """打开动画音乐文件夹"""
- folder_path = get_resources_path(ANIMATION_MUSIC_FOLDER)
- if not folder_path.exists():
- os.makedirs(folder_path)
- if folder_path:
- QDesktopServices.openUrl(QUrl.fromLocalFile(str(folder_path)))
- else:
- logger.error("无法获取动画音乐文件夹路径")
-
- def open_result_music_folder(self):
- """打开结果音乐文件夹"""
- folder_path = get_resources_path(RESULT_MUSIC_FOLDER)
- if not folder_path.exists():
- os.makedirs(folder_path)
- if folder_path:
- QDesktopServices.openUrl(QUrl.fromLocalFile(str(folder_path)))
- else:
- logger.error("无法获取结果音乐文件夹路径")
\ No newline at end of file
diff --git a/app/view/settings/extraction_settings/quick_draw_settings.py b/app/view/settings/extraction_settings/quick_draw_settings.py
index 9fc8bf59..03775926 100644
--- a/app/view/settings/extraction_settings/quick_draw_settings.py
+++ b/app/view/settings/extraction_settings/quick_draw_settings.py
@@ -1,16 +1,13 @@
# ==================================================
# 导入库
# ==================================================
-import json
import os
-import sys
-import subprocess
from loguru import logger
-from PyQt6.QtWidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from PySide6.QtNetwork import *
from qfluentwidgets import *
from app.tools.variable import *
@@ -20,6 +17,7 @@
from app.tools.settings_access import *
from app.Language.obtain_language import *
+
# ==================================================
# 闪抽设置
# ==================================================
@@ -47,100 +45,196 @@ def __init__(self, parent=None):
class quick_draw_extraction_function(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("quick_draw_settings", "extraction_function"))
+ self.setTitle(
+ get_content_name_async("quick_draw_settings", "extraction_function")
+ )
self.setBorderRadius(8)
- # 抽取模式下拉框
+ # 抽取模式下拉框(延迟填充)
self.draw_mode_combo = ComboBox()
- self.draw_mode_combo.addItems(get_content_combo_name_async("quick_draw_settings", "draw_mode"))
- self.draw_mode_combo.setCurrentIndex(readme_settings_async("quick_draw_settings", "draw_mode"))
self.draw_mode_combo.currentIndexChanged.connect(self.on_draw_mode_changed)
- # 清除抽取记录方式下拉框
+ # 清除抽取记录方式下拉框(延迟填充)
self.clear_record_combo = ComboBox()
- self.clear_record_combo.addItems(get_content_combo_name_async("quick_draw_settings", "clear_record"))
- self.clear_record_combo.setCurrentIndex(readme_settings_async("quick_draw_settings", "clear_record"))
- self.clear_record_combo.currentIndexChanged.connect(lambda: update_settings("quick_draw_settings", "clear_record", self.clear_record_combo.currentIndex()))
+ self.clear_record_combo.currentIndexChanged.connect(
+ lambda: update_settings(
+ "quick_draw_settings",
+ "clear_record",
+ self.clear_record_combo.currentIndex(),
+ )
+ )
# 半重复抽取次数输入框
self.half_repeat_spin = SpinBox()
self.half_repeat_spin.setFixedWidth(WIDTH_SPINBOX)
self.half_repeat_spin.setRange(0, 100)
self.half_repeat_spin.setSuffix("次")
- self.half_repeat_spin.setValue(readme_settings_async("quick_draw_settings", "half_repeat"))
- self.half_repeat_spin.valueChanged.connect(lambda: update_settings("quick_draw_settings", "half_repeat", self.half_repeat_spin.value()))
-
- # 抽取后定时清除时间输入框
- self.clear_time_spin = SpinBox()
- self.clear_time_spin.setFixedWidth(WIDTH_SPINBOX)
- self.clear_time_spin.setRange(0, 25600)
- self.clear_time_spin.setSuffix("秒")
- self.clear_time_spin.setValue(readme_settings_async("quick_draw_settings", "clear_time"))
- self.clear_time_spin.valueChanged.connect(lambda: update_settings("quick_draw_settings", "clear_time", self.clear_time_spin.value()))
-
- # 抽取方式下拉框
+ self.half_repeat_spin.setValue(
+ readme_settings_async("quick_draw_settings", "half_repeat")
+ )
+ self.half_repeat_spin.valueChanged.connect(
+ lambda: update_settings(
+ "quick_draw_settings", "half_repeat", self.half_repeat_spin.value()
+ )
+ )
+
+ # 抽取方式下拉框(延迟填充)
self.draw_type_combo = ComboBox()
- self.draw_type_combo.addItems(get_content_combo_name_async("quick_draw_settings", "draw_type"))
- self.draw_type_combo.setCurrentIndex(readme_settings_async("quick_draw_settings", "draw_type"))
- self.draw_type_combo.currentIndexChanged.connect(lambda: update_settings("quick_draw_settings", "draw_type", self.draw_type_combo.currentIndex()))
+ self.draw_type_combo.currentIndexChanged.connect(
+ lambda: update_settings(
+ "quick_draw_settings", "draw_type", self.draw_type_combo.currentIndex()
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_document_bullet_list_cube_20_filled"),
- get_content_name_async("quick_draw_settings", "draw_mode"), get_content_description_async("quick_draw_settings", "draw_mode"), self.draw_mode_combo)
- self.addGroup(get_theme_icon("ic_fluent_text_clear_formatting_20_filled"),
- get_content_name_async("quick_draw_settings", "clear_record"), get_content_description_async("quick_draw_settings", "clear_record"), self.clear_record_combo)
- self.addGroup(get_theme_icon("ic_fluent_clipboard_bullet_list_20_filled"),
- get_content_name_async("quick_draw_settings", "half_repeat"), get_content_description_async("quick_draw_settings", "half_repeat"), self.half_repeat_spin)
- self.addGroup(get_theme_icon("ic_fluent_timer_off_20_filled"),
- get_content_name_async("quick_draw_settings", "clear_time"), get_content_description_async("quick_draw_settings", "clear_time"), self.clear_time_spin)
- self.addGroup(get_theme_icon("ic_fluent_drawer_add_20_filled"),
- get_content_name_async("quick_draw_settings", "draw_type"), get_content_description_async("quick_draw_settings", "draw_type"), self.draw_type_combo)
-
- # 初始化时调用一次,确保界面状态与设置一致
- self.on_draw_mode_changed()
+ self.addGroup(
+ get_theme_icon("ic_fluent_document_bullet_list_cube_20_filled"),
+ get_content_name_async("quick_draw_settings", "draw_mode"),
+ get_content_description_async("quick_draw_settings", "draw_mode"),
+ self.draw_mode_combo,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_text_clear_formatting_20_filled"),
+ get_content_name_async("quick_draw_settings", "clear_record"),
+ get_content_description_async("quick_draw_settings", "clear_record"),
+ self.clear_record_combo,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_clipboard_bullet_list_20_filled"),
+ get_content_name_async("quick_draw_settings", "half_repeat"),
+ get_content_description_async("quick_draw_settings", "half_repeat"),
+ self.half_repeat_spin,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_drawer_add_20_filled"),
+ get_content_name_async("quick_draw_settings", "draw_type"),
+ get_content_description_async("quick_draw_settings", "draw_type"),
+ self.draw_type_combo,
+ )
+
+ # 初始化时先在后台加载所有需要的选项并回填
+ QTimer.singleShot(0, self._start_background_load)
+
+ def _start_background_load(self):
+ class _Signals(QObject):
+ loaded = Signal(dict)
+
+ class _Loader(QRunnable):
+ def __init__(self, fn, signals):
+ super().__init__()
+ self.fn = fn
+ self.signals = signals
+
+ def run(self):
+ try:
+ data = self.fn()
+ self.signals.loaded.emit(data)
+ except Exception as e:
+ logger.error(f"后台加载 quick_draw_settings 数据失败: {e}")
+
+ def _collect():
+ data = {}
+ try:
+ data["draw_mode_items"] = get_content_combo_name_async(
+ "quick_draw_settings", "draw_mode"
+ )
+ data["draw_mode_index"] = readme_settings_async(
+ "quick_draw_settings", "draw_mode"
+ )
+ data["clear_record_items"] = get_content_combo_name_async(
+ "quick_draw_settings", "clear_record"
+ )
+ data["clear_record_index"] = readme_settings_async(
+ "quick_draw_settings", "clear_record"
+ )
+ data["half_repeat_value"] = readme_settings_async(
+ "quick_draw_settings", "half_repeat"
+ )
+ data["draw_type_items"] = get_content_combo_name_async(
+ "quick_draw_settings", "draw_type"
+ )
+ data["draw_type_index"] = readme_settings_async(
+ "quick_draw_settings", "draw_type"
+ )
+ except Exception as e:
+ logger.error(f"收集 quick_draw_settings 初始数据失败: {e}")
+ return data
+
+ signals = _Signals()
+ signals.loaded.connect(self._on_background_loaded)
+ runnable = _Loader(_collect, signals)
+ QThreadPool.globalInstance().start(runnable)
+
+ def _on_background_loaded(self, data: dict):
+ try:
+ if "draw_mode_items" in data:
+ self.draw_mode_combo.addItems(data.get("draw_mode_items", []))
+ self.draw_mode_combo.setCurrentIndex(data.get("draw_mode_index", 0))
+ if "clear_record_items" in data:
+ self.clear_record_combo.addItems(data.get("clear_record_items", []))
+ self.clear_record_combo.setCurrentIndex(
+ data.get("clear_record_index", 0)
+ )
+ if "half_repeat_value" in data:
+ self.half_repeat_spin.setValue(data.get("half_repeat_value", 0))
+ if "draw_type_items" in data:
+ self.draw_type_combo.addItems(data.get("draw_type_items", []))
+ self.draw_type_combo.setCurrentIndex(data.get("draw_type_index", 0))
+
+ self.on_draw_mode_changed()
+ except Exception as e:
+ logger.error(f"回填 quick_draw_settings 数据失败: {e}")
def on_draw_mode_changed(self):
"""当抽取模式改变时的处理逻辑"""
# 更新设置值
- update_settings("quick_draw_settings", "draw_mode", self.draw_mode_combo.currentIndex())
-
+ update_settings(
+ "quick_draw_settings", "draw_mode", self.draw_mode_combo.currentIndex()
+ )
+
# 获取当前抽取模式索引
draw_mode_index = self.draw_mode_combo.currentIndex()
-
+
# 根据抽取模式设置不同的控制逻辑
if draw_mode_index == 0: # 重复抽取模式
# 禁用清除抽取记录方式下拉框
self.clear_record_combo.setEnabled(False)
# 清空当前选项
self.clear_record_combo.clear()
- self.clear_record_combo.addItems(get_any_position_value_async("quick_draw_settings", "clear_record", "combo_items_other"))
+ self.clear_record_combo.addItems(
+ get_any_position_value_async(
+ "quick_draw_settings", "clear_record", "combo_items_other"
+ )
+ )
# 强制设置为"无需清除"(索引2)
self.clear_record_combo.setCurrentIndex(2)
# 更新设置
update_settings("quick_draw_settings", "clear_record", 2)
-
+
# 设置half_repeat_spin为0并禁用
self.half_repeat_spin.setEnabled(False)
self.half_repeat_spin.setRange(0, 0)
self.half_repeat_spin.setValue(0)
# 更新设置
update_settings("quick_draw_settings", "half_repeat", 0)
-
+
else: # 不重复抽取模式或半重复抽取模式
# 启用清除抽取记录方式下拉框
self.clear_record_combo.setEnabled(True)
-
+
# 清空当前选项
self.clear_record_combo.clear()
-
+
# 添加前两个选项(不包含"无需清除")
- self.clear_record_combo.addItems(get_content_combo_name_async("quick_draw_settings", "clear_record"))
-
+ self.clear_record_combo.addItems(
+ get_content_combo_name_async("quick_draw_settings", "clear_record")
+ )
+
# 设置默认选择第一个选项
self.clear_record_combo.setCurrentIndex(0)
# 更新设置
update_settings("quick_draw_settings", "clear_record", 0)
-
+
# 根据具体模式设置half_repeat_spin
if draw_mode_index == 1: # 不重复抽取模式
# 设置half_repeat_spin为1并禁用
@@ -171,28 +265,66 @@ def __init__(self, parent=None):
self.font_size_spin.setFixedWidth(WIDTH_SPINBOX)
self.font_size_spin.setRange(10, 1000)
self.font_size_spin.setSuffix("px")
- self.font_size_spin.setValue(readme_settings_async("quick_draw_settings", "font_size"))
- self.font_size_spin.valueChanged.connect(lambda: update_settings("quick_draw_settings", "font_size", self.font_size_spin.value()))
+ self.font_size_spin.setValue(
+ readme_settings_async("quick_draw_settings", "font_size")
+ )
+ self.font_size_spin.valueChanged.connect(
+ lambda: update_settings(
+ "quick_draw_settings", "font_size", self.font_size_spin.value()
+ )
+ )
# 结果显示格式下拉框
self.display_format_combo = ComboBox()
- self.display_format_combo.addItems(get_content_combo_name_async("quick_draw_settings", "display_format"))
- self.display_format_combo.setCurrentIndex(readme_settings_async("quick_draw_settings", "display_format"))
- self.display_format_combo.currentIndexChanged.connect(lambda: update_settings("quick_draw_settings", "display_format", self.display_format_combo.currentIndex()))
+ self.display_format_combo.addItems(
+ get_content_combo_name_async("quick_draw_settings", "display_format")
+ )
+ self.display_format_combo.setCurrentIndex(
+ readme_settings_async("quick_draw_settings", "display_format")
+ )
+ self.display_format_combo.currentIndexChanged.connect(
+ lambda: update_settings(
+ "quick_draw_settings",
+ "display_format",
+ self.display_format_combo.currentIndex(),
+ )
+ )
# 显示随机组员格式下拉框
self.show_random_format_combo = ComboBox()
- self.show_random_format_combo.addItems(get_content_combo_name_async("quick_draw_settings", "show_random"))
- self.show_random_format_combo.setCurrentIndex(readme_settings_async("quick_draw_settings", "show_random"))
- self.show_random_format_combo.currentIndexChanged.connect(lambda: update_settings("quick_draw_settings", "show_random", self.show_random_format_combo.currentIndex()))
+ self.show_random_format_combo.addItems(
+ get_content_combo_name_async("quick_draw_settings", "show_random")
+ )
+ self.show_random_format_combo.setCurrentIndex(
+ readme_settings_async("quick_draw_settings", "show_random")
+ )
+ self.show_random_format_combo.currentIndexChanged.connect(
+ lambda: update_settings(
+ "quick_draw_settings",
+ "show_random",
+ self.show_random_format_combo.currentIndex(),
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_text_font_20_filled"),
- get_content_name_async("quick_draw_settings", "font_size"), get_content_description_async("quick_draw_settings", "font_size"), self.font_size_spin)
- self.addGroup(get_theme_icon("ic_fluent_slide_text_sparkle_20_filled"),
- get_content_name_async("quick_draw_settings", "display_format"), get_content_description_async("quick_draw_settings", "display_format"), self.display_format_combo)
- self.addGroup(get_theme_icon("ic_fluent_group_list_20_filled"),
- get_content_name_async("quick_draw_settings", "show_random"), get_content_description_async("quick_draw_settings", "show_random"), self.show_random_format_combo)
+ self.addGroup(
+ get_theme_icon("ic_fluent_text_font_20_filled"),
+ get_content_name_async("quick_draw_settings", "font_size"),
+ get_content_description_async("quick_draw_settings", "font_size"),
+ self.font_size_spin,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_slide_text_sparkle_20_filled"),
+ get_content_name_async("quick_draw_settings", "display_format"),
+ get_content_description_async("quick_draw_settings", "display_format"),
+ self.display_format_combo,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_group_list_20_filled"),
+ get_content_name_async("quick_draw_settings", "show_random"),
+ get_content_description_async("quick_draw_settings", "show_random"),
+ self.show_random_format_combo,
+ )
class quick_draw_animation_settings(QWidget):
@@ -215,120 +347,247 @@ def __init__(self, parent=None):
self.student_image_widget = quick_draw_student_image_settings(self)
self.vBoxLayout.addWidget(self.student_image_widget)
- # 添加音乐设置组件
- self.music_settings_widget = quick_draw_music_settings(self)
- self.vBoxLayout.addWidget(self.music_settings_widget)
-
class quick_draw_basic_animation_settings(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("quick_draw_settings", "basic_animation_settings"))
+ self.setTitle(
+ get_content_name_async("quick_draw_settings", "basic_animation_settings")
+ )
self.setBorderRadius(8)
# 动画模式下拉框
self.animation_combo = ComboBox()
- self.animation_combo.addItems(get_content_combo_name_async("quick_draw_settings", "animation"))
- self.animation_combo.setCurrentIndex(readme_settings_async("quick_draw_settings", "animation"))
- self.animation_combo.currentIndexChanged.connect(lambda: update_settings("quick_draw_settings", "animation", self.animation_combo.currentIndex()))
+ self.animation_combo.addItems(
+ get_content_combo_name_async("quick_draw_settings", "animation")
+ )
+ self.animation_combo.setCurrentIndex(
+ readme_settings_async("quick_draw_settings", "animation")
+ )
+ self.animation_combo.currentIndexChanged.connect(
+ lambda: update_settings(
+ "quick_draw_settings", "animation", self.animation_combo.currentIndex()
+ )
+ )
# 动画间隔输入框
self.animation_interval_spin = SpinBox()
self.animation_interval_spin.setFixedWidth(WIDTH_SPINBOX)
self.animation_interval_spin.setRange(1, 2000)
self.animation_interval_spin.setSuffix("ms")
- self.animation_interval_spin.setValue(readme_settings_async("quick_draw_settings", "animation_interval"))
- self.animation_interval_spin.valueChanged.connect(lambda: update_settings("quick_draw_settings", "animation_interval", self.animation_interval_spin.value()))
+ self.animation_interval_spin.setValue(
+ readme_settings_async("quick_draw_settings", "animation_interval")
+ )
+ self.animation_interval_spin.valueChanged.connect(
+ lambda: update_settings(
+ "quick_draw_settings",
+ "animation_interval",
+ self.animation_interval_spin.value(),
+ )
+ )
# 自动播放次数输入框
self.autoplay_count_spin = SpinBox()
self.autoplay_count_spin.setFixedWidth(WIDTH_SPINBOX)
self.autoplay_count_spin.setRange(0, 100)
self.autoplay_count_spin.setSuffix("次")
- self.autoplay_count_spin.setValue(readme_settings_async("quick_draw_settings", "autoplay_count"))
- self.autoplay_count_spin.valueChanged.connect(lambda: update_settings("quick_draw_settings", "autoplay_count", self.autoplay_count_spin.value()))
+ self.autoplay_count_spin.setValue(
+ readme_settings_async("quick_draw_settings", "autoplay_count")
+ )
+ self.autoplay_count_spin.valueChanged.connect(
+ lambda: update_settings(
+ "quick_draw_settings",
+ "autoplay_count",
+ self.autoplay_count_spin.value(),
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_sanitize_20_filled"),
- get_content_name_async("quick_draw_settings", "animation"), get_content_description_async("quick_draw_settings", "animation"), self.animation_combo)
- self.addGroup(get_theme_icon("ic_fluent_timeline_20_filled"),
- get_content_name_async("quick_draw_settings", "animation_interval"), get_content_description_async("quick_draw_settings", "animation_interval"), self.animation_interval_spin)
- self.addGroup(get_theme_icon("ic_fluent_slide_play_20_filled"),
- get_content_name_async("quick_draw_settings", "autoplay_count"), get_content_description_async("quick_draw_settings", "autoplay_count"), self.autoplay_count_spin)
+ self.addGroup(
+ get_theme_icon("ic_fluent_sanitize_20_filled"),
+ get_content_name_async("quick_draw_settings", "animation"),
+ get_content_description_async("quick_draw_settings", "animation"),
+ self.animation_combo,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_timeline_20_filled"),
+ get_content_name_async("quick_draw_settings", "animation_interval"),
+ get_content_description_async("quick_draw_settings", "animation_interval"),
+ self.animation_interval_spin,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_slide_play_20_filled"),
+ get_content_name_async("quick_draw_settings", "autoplay_count"),
+ get_content_description_async("quick_draw_settings", "autoplay_count"),
+ self.autoplay_count_spin,
+ )
class quick_draw_color_theme_settings(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("quick_draw_settings", "color_theme_settings"))
+ self.setTitle(
+ get_content_name_async("quick_draw_settings", "color_theme_settings")
+ )
self.setBorderRadius(8)
# 动画颜色主题下拉框
self.animation_color_theme_combo = ComboBox()
- self.animation_color_theme_combo.addItems(get_content_combo_name_async("quick_draw_settings", "animation_color_theme"))
- self.animation_color_theme_combo.setCurrentIndex(readme_settings_async("quick_draw_settings", "animation_color_theme"))
- self.animation_color_theme_combo.currentIndexChanged.connect(lambda: update_settings("quick_draw_settings", "animation_color_theme", self.animation_color_theme_combo.currentIndex()))
+ self.animation_color_theme_combo.addItems(
+ get_content_combo_name_async("quick_draw_settings", "animation_color_theme")
+ )
+ self.animation_color_theme_combo.setCurrentIndex(
+ readme_settings_async("quick_draw_settings", "animation_color_theme")
+ )
+ self.animation_color_theme_combo.currentIndexChanged.connect(
+ lambda: update_settings(
+ "quick_draw_settings",
+ "animation_color_theme",
+ self.animation_color_theme_combo.currentIndex(),
+ )
+ )
# 结果颜色主题下拉框
self.result_color_theme_combo = ComboBox()
- self.result_color_theme_combo.addItems(get_content_combo_name_async("quick_draw_settings", "result_color_theme"))
- self.result_color_theme_combo.setCurrentIndex(readme_settings_async("quick_draw_settings", "result_color_theme"))
- self.result_color_theme_combo.currentIndexChanged.connect(lambda: update_settings("quick_draw_settings", "result_color_theme", self.result_color_theme_combo.currentIndex()))
+ self.result_color_theme_combo.addItems(
+ get_content_combo_name_async("quick_draw_settings", "result_color_theme")
+ )
+ self.result_color_theme_combo.setCurrentIndex(
+ readme_settings_async("quick_draw_settings", "result_color_theme")
+ )
+ self.result_color_theme_combo.currentIndexChanged.connect(
+ lambda: update_settings(
+ "quick_draw_settings",
+ "result_color_theme",
+ self.result_color_theme_combo.currentIndex(),
+ )
+ )
# 动画固定颜色
- self.animation_fixed_color_button = ColorConfigItem("Theme", "Color", readme_settings_async("quick_draw_settings", "animation_fixed_color"))
- self.animation_fixed_color_button.valueChanged.connect(lambda color: update_settings("quick_draw_settings", "animation_fixed_color", color.name()))
-
+ self.animation_fixed_color_button = ColorConfigItem(
+ "Theme",
+ "Color",
+ readme_settings_async("quick_draw_settings", "animation_fixed_color"),
+ )
+ self.animation_fixed_color_button.valueChanged.connect(
+ lambda color: update_settings(
+ "quick_draw_settings", "animation_fixed_color", color.name()
+ )
+ )
+
# 结果固定颜色
- self.result_fixed_color_button = ColorConfigItem("Theme", "Color", readme_settings_async("quick_draw_settings", "result_fixed_color"))
- self.result_fixed_color_button.valueChanged.connect(lambda color: update_settings("quick_draw_settings", "result_fixed_color", color.name()))
+ self.result_fixed_color_button = ColorConfigItem(
+ "Theme",
+ "Color",
+ readme_settings_async("quick_draw_settings", "result_fixed_color"),
+ )
+ self.result_fixed_color_button.valueChanged.connect(
+ lambda color: update_settings(
+ "quick_draw_settings", "result_fixed_color", color.name()
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_color_20_filled"),
- get_content_name_async("quick_draw_settings", "animation_color_theme"), get_content_description_async("quick_draw_settings", "animation_color_theme"), self.animation_color_theme_combo)
- self.addGroup(get_theme_icon("ic_fluent_color_20_filled"),
- get_content_name_async("quick_draw_settings", "result_color_theme"), get_content_description_async("quick_draw_settings", "result_color_theme"), self.result_color_theme_combo)
+ self.addGroup(
+ get_theme_icon("ic_fluent_color_20_filled"),
+ get_content_name_async("quick_draw_settings", "animation_color_theme"),
+ get_content_description_async(
+ "quick_draw_settings", "animation_color_theme"
+ ),
+ self.animation_color_theme_combo,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_color_20_filled"),
+ get_content_name_async("quick_draw_settings", "result_color_theme"),
+ get_content_description_async("quick_draw_settings", "result_color_theme"),
+ self.result_color_theme_combo,
+ )
self.animationColorCard = ColorSettingCard(
self.animation_fixed_color_button,
get_theme_icon("ic_fluent_text_color_20_filled"),
- self.tr(get_content_name_async("quick_draw_settings", "animation_fixed_color")),
- self.tr(get_content_description_async("quick_draw_settings", "animation_fixed_color")),
- self
+ self.tr(
+ get_content_name_async("quick_draw_settings", "animation_fixed_color")
+ ),
+ self.tr(
+ get_content_description_async(
+ "quick_draw_settings", "animation_fixed_color"
+ )
+ ),
+ self,
)
self.resultColorCard = ColorSettingCard(
self.result_fixed_color_button,
get_theme_icon("ic_fluent_text_color_20_filled"),
- self.tr(get_content_name_async("quick_draw_settings", "result_fixed_color")),
- self.tr(get_content_description_async("quick_draw_settings", "result_fixed_color")),
- self
+ self.tr(
+ get_content_name_async("quick_draw_settings", "result_fixed_color")
+ ),
+ self.tr(
+ get_content_description_async(
+ "quick_draw_settings", "result_fixed_color"
+ )
+ ),
+ self,
)
self.vBoxLayout.addWidget(self.animationColorCard)
self.vBoxLayout.addWidget(self.resultColorCard)
+
class quick_draw_student_image_settings(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("quick_draw_settings", "student_image_settings"))
+ self.setTitle(
+ get_content_name_async("quick_draw_settings", "student_image_settings")
+ )
self.setBorderRadius(8)
# 学生图片开关
self.student_image_switch = SwitchButton()
- self.student_image_switch.setOffText(get_content_switchbutton_name_async("quick_draw_settings", "student_image", "disable"))
- self.student_image_switch.setOnText(get_content_switchbutton_name_async("quick_draw_settings", "student_image", "enable"))
- self.student_image_switch.setChecked(readme_settings_async("quick_draw_settings", "student_image"))
- self.student_image_switch.checkedChanged.connect(lambda: update_settings("quick_draw_settings", "student_image", self.student_image_switch.isChecked()))
+ self.student_image_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "quick_draw_settings", "student_image", "disable"
+ )
+ )
+ self.student_image_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "quick_draw_settings", "student_image", "enable"
+ )
+ )
+ self.student_image_switch.setChecked(
+ readme_settings_async("quick_draw_settings", "student_image")
+ )
+ self.student_image_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "quick_draw_settings",
+ "student_image",
+ self.student_image_switch.isChecked(),
+ )
+ )
# 打开学生图片文件夹按钮
- self.open_student_image_folder_button = PushButton(get_content_name_async("quick_draw_settings", "open_student_image_folder"))
- self.open_student_image_folder_button.clicked.connect(lambda: self.open_student_image_folder())
+ self.open_student_image_folder_button = PushButton(
+ get_content_name_async("quick_draw_settings", "open_student_image_folder")
+ )
+ self.open_student_image_folder_button.clicked.connect(
+ lambda: self.open_student_image_folder()
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_image_circle_20_filled"),
- get_content_name_async("quick_draw_settings", "student_image"), get_content_description_async("quick_draw_settings", "student_image"), self.student_image_switch)
- self.addGroup(get_theme_icon("ic_fluent_folder_open_20_filled"),
- get_content_name_async("quick_draw_settings", "open_student_image_folder"), get_content_description_async("quick_draw_settings", "open_student_image_folder"), self.open_student_image_folder_button)
+ self.addGroup(
+ get_theme_icon("ic_fluent_image_circle_20_filled"),
+ get_content_name_async("quick_draw_settings", "student_image"),
+ get_content_description_async("quick_draw_settings", "student_image"),
+ self.student_image_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_folder_open_20_filled"),
+ get_content_name_async("quick_draw_settings", "open_student_image_folder"),
+ get_content_description_async(
+ "quick_draw_settings", "open_student_image_folder"
+ ),
+ self.open_student_image_folder_button,
+ )
def open_student_image_folder(self):
"""打开学生图片文件夹"""
@@ -339,121 +598,3 @@ def open_student_image_folder(self):
QDesktopServices.openUrl(QUrl.fromLocalFile(str(folder_path)))
else:
logger.error("无法获取学生图片文件夹路径")
-
-class quick_draw_music_settings(GroupHeaderCardWidget):
- def __init__(self, parent=None):
- super().__init__(parent)
- self.setTitle(get_content_name_async("quick_draw_settings", "music_settings"))
- self.setBorderRadius(8)
-
- # 动画音乐开关
- self.animation_music_switch = SwitchButton()
- self.animation_music_switch.setOffText(get_content_switchbutton_name_async("quick_draw_settings", "animation_music", "disable"))
- self.animation_music_switch.setOnText(get_content_switchbutton_name_async("quick_draw_settings", "animation_music", "enable"))
- self.animation_music_switch.setChecked(readme_settings_async("quick_draw_settings", "animation_music"))
- self.animation_music_switch.checkedChanged.connect(lambda: update_settings("quick_draw_settings", "animation_music", self.animation_music_switch.isChecked()))
-
- # 结果音乐开关
- self.result_music_switch = SwitchButton()
- self.result_music_switch.setOffText(get_content_switchbutton_name_async("quick_draw_settings", "result_music", "disable"))
- self.result_music_switch.setOnText(get_content_switchbutton_name_async("quick_draw_settings", "result_music", "enable"))
- self.result_music_switch.setChecked(readme_settings_async("quick_draw_settings", "result_music"))
- self.result_music_switch.checkedChanged.connect(lambda: update_settings("quick_draw_settings", "result_music", self.result_music_switch.isChecked()))
-
- # 动画音乐文件夹按钮
- self.open_animation_music_folder_button = PushButton(get_content_name_async("quick_draw_settings", "open_animation_music_folder"))
- self.open_animation_music_folder_button.clicked.connect(lambda: self.open_animation_music_folder())
-
- # 结果音乐文件夹按钮
- self.open_result_music_folder_button = PushButton(get_content_name_async("quick_draw_settings", "open_result_music_folder"))
- self.open_result_music_folder_button.clicked.connect(lambda: self.open_result_music_folder())
-
- # 动画音乐音量
- self.animation_music_volume_spin = SpinBox()
- self.animation_music_volume_spin.setFixedWidth(WIDTH_SPINBOX)
- self.animation_music_volume_spin.setRange(0, 100)
- self.animation_music_volume_spin.setSuffix("%")
- self.animation_music_volume_spin.setValue(readme_settings_async("quick_draw_settings", "animation_music_volume"))
- self.animation_music_volume_spin.valueChanged.connect(lambda: update_settings("quick_draw_settings", "animation_music_volume", self.animation_music_volume_spin.value()))
-
- # 结果音乐音量
- self.result_music_volume_spin = SpinBox()
- self.result_music_volume_spin.setFixedWidth(WIDTH_SPINBOX)
- self.result_music_volume_spin.setRange(0, 100)
- self.result_music_volume_spin.setSuffix("%")
- self.result_music_volume_spin.setValue(readme_settings_async("quick_draw_settings", "result_music_volume"))
- self.result_music_volume_spin.valueChanged.connect(lambda: update_settings("quick_draw_settings", "result_music_volume", self.result_music_volume_spin.value()))
-
- # 动画音乐淡入时间
- self.animation_music_fade_in_spin = SpinBox()
- self.animation_music_fade_in_spin.setFixedWidth(WIDTH_SPINBOX)
- self.animation_music_fade_in_spin.setRange(0, 1000)
- self.animation_music_fade_in_spin.setSuffix("ms")
- self.animation_music_fade_in_spin.setValue(readme_settings_async("quick_draw_settings", "animation_music_fade_in"))
- self.animation_music_fade_in_spin.valueChanged.connect(lambda: update_settings("quick_draw_settings", "animation_music_fade_in", self.animation_music_fade_in_spin.value()))
-
- # 结果音乐淡入时间
- self.result_music_fade_in_spin = SpinBox()
- self.result_music_fade_in_spin.setFixedWidth(WIDTH_SPINBOX)
- self.result_music_fade_in_spin.setRange(0, 1000)
- self.result_music_fade_in_spin.setSuffix("ms")
- self.result_music_fade_in_spin.setValue(readme_settings_async("quick_draw_settings", "result_music_fade_in"))
- self.result_music_fade_in_spin.valueChanged.connect(lambda: update_settings("quick_draw_settings", "result_music_fade_in", self.result_music_fade_in_spin.value()))
-
- # 动画音乐淡出时间
- self.animation_music_fade_out_spin = SpinBox()
- self.animation_music_fade_out_spin.setFixedWidth(WIDTH_SPINBOX)
- self.animation_music_fade_out_spin.setRange(0, 1000)
- self.animation_music_fade_out_spin.setSuffix("ms")
- self.animation_music_fade_out_spin.setValue(readme_settings_async("quick_draw_settings", "animation_music_fade_out"))
- self.animation_music_fade_out_spin.valueChanged.connect(lambda: update_settings("quick_draw_settings", "animation_music_fade_out", self.animation_music_fade_out_spin.value()))
-
- # 结果音乐淡出时间
- self.result_music_fade_out_spin = SpinBox()
- self.result_music_fade_out_spin.setFixedWidth(WIDTH_SPINBOX)
- self.result_music_fade_out_spin.setRange(0, 1000)
- self.result_music_fade_out_spin.setSuffix("ms")
- self.result_music_fade_out_spin.setValue(readme_settings_async("quick_draw_settings", "result_music_fade_out"))
- self.result_music_fade_out_spin.valueChanged.connect(lambda: update_settings("quick_draw_settings", "result_music_fade_out", self.result_music_fade_out_spin.value()))
-
- # 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_music_note_2_20_filled"),
- get_content_name_async("quick_draw_settings", "animation_music"), get_content_description_async("quick_draw_settings", "animation_music"), self.animation_music_switch)
- self.addGroup(get_theme_icon("ic_fluent_music_note_2_20_filled"),
- get_content_name_async("quick_draw_settings", "result_music"), get_content_description_async("quick_draw_settings", "result_music"), self.result_music_switch)
- self.addGroup(get_theme_icon("ic_fluent_folder_open_20_filled"),
- get_content_name_async("quick_draw_settings", "open_animation_music_folder"), get_content_description_async("quick_draw_settings", "open_animation_music_folder"), self.open_animation_music_folder_button)
- self.addGroup(get_theme_icon("ic_fluent_folder_open_20_filled"),
- get_content_name_async("quick_draw_settings", "open_result_music_folder"), get_content_description_async("quick_draw_settings", "open_result_music_folder"), self.open_result_music_folder_button)
- self.addGroup(get_theme_icon("ic_fluent_speaker_2_20_filled"),
- get_content_name_async("quick_draw_settings", "animation_music_volume"), get_content_description_async("quick_draw_settings", "animation_music_volume"), self.animation_music_volume_spin)
- self.addGroup(get_theme_icon("ic_fluent_speaker_2_20_filled"),
- get_content_name_async("quick_draw_settings", "result_music_volume"), get_content_description_async("quick_draw_settings", "result_music_volume"), self.result_music_volume_spin)
- self.addGroup(get_theme_icon("ic_fluent_arrow_up_20_filled"),
- get_content_name_async("quick_draw_settings", "animation_music_fade_in"), get_content_description_async("quick_draw_settings", "animation_music_fade_in"), self.animation_music_fade_in_spin)
- self.addGroup(get_theme_icon("ic_fluent_arrow_up_20_filled"),
- get_content_name_async("quick_draw_settings", "result_music_fade_in"), get_content_description_async("quick_draw_settings", "result_music_fade_in"), self.result_music_fade_in_spin)
- self.addGroup(get_theme_icon("ic_fluent_arrow_down_20_filled"),
- get_content_name_async("quick_draw_settings", "animation_music_fade_out"), get_content_description_async("quick_draw_settings", "animation_music_fade_out"), self.animation_music_fade_out_spin)
- self.addGroup(get_theme_icon("ic_fluent_arrow_down_20_filled"),
- get_content_name_async("quick_draw_settings", "result_music_fade_out"), get_content_description_async("quick_draw_settings", "result_music_fade_out"), self.result_music_fade_out_spin)
-
- def open_animation_music_folder(self):
- """打开动画音乐文件夹"""
- folder_path = get_resources_path(ANIMATION_MUSIC_FOLDER)
- if not folder_path.exists():
- os.makedirs(folder_path)
- if folder_path:
- QDesktopServices.openUrl(QUrl.fromLocalFile(str(folder_path)))
- else:
- logger.error("无法获取动画音乐文件夹路径")
-
- def open_result_music_folder(self):
- """打开结果音乐文件夹"""
- folder_path = get_resources_path(RESULT_MUSIC_FOLDER)
- if not folder_path.exists():
- os.makedirs(folder_path)
- if folder_path:
- QDesktopServices.openUrl(QUrl.fromLocalFile(str(folder_path)))
- else:
- logger.error("无法获取结果音乐文件夹路径")
\ No newline at end of file
diff --git a/app/view/settings/extraction_settings/roll_call_settings.py b/app/view/settings/extraction_settings/roll_call_settings.py
index 89bc0744..2bd6c3af 100644
--- a/app/view/settings/extraction_settings/roll_call_settings.py
+++ b/app/view/settings/extraction_settings/roll_call_settings.py
@@ -1,16 +1,13 @@
# ==================================================
# 导入库
# ==================================================
-import json
import os
-import sys
-import subprocess
from loguru import logger
-from PyQt6.QtWidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from PySide6.QtNetwork import *
from qfluentwidgets import *
from app.tools.variable import *
@@ -20,6 +17,7 @@
from app.tools.settings_access import *
from app.Language.obtain_language import *
+
# ==================================================
# 点名设置
# ==================================================
@@ -47,100 +45,144 @@ def __init__(self, parent=None):
class roll_call_extraction_function(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("roll_call_settings", "extraction_function"))
+ self.setTitle(
+ get_content_name_async("roll_call_settings", "extraction_function")
+ )
self.setBorderRadius(8)
# 抽取模式下拉框
self.draw_mode_combo = ComboBox()
- self.draw_mode_combo.addItems(get_content_combo_name_async("roll_call_settings", "draw_mode"))
- self.draw_mode_combo.setCurrentIndex(readme_settings_async("roll_call_settings", "draw_mode"))
+ self.draw_mode_combo.addItems(
+ get_content_combo_name_async("roll_call_settings", "draw_mode")
+ )
+ self.draw_mode_combo.setCurrentIndex(
+ readme_settings_async("roll_call_settings", "draw_mode")
+ )
self.draw_mode_combo.currentIndexChanged.connect(self.on_draw_mode_changed)
# 清除抽取记录方式下拉框
self.clear_record_combo = ComboBox()
- self.clear_record_combo.addItems(get_content_combo_name_async("roll_call_settings", "clear_record"))
- self.clear_record_combo.setCurrentIndex(readme_settings_async("roll_call_settings", "clear_record"))
- self.clear_record_combo.currentIndexChanged.connect(lambda: update_settings("roll_call_settings", "clear_record", self.clear_record_combo.currentIndex()))
+ self.clear_record_combo.addItems(
+ get_content_combo_name_async("roll_call_settings", "clear_record")
+ )
+ self.clear_record_combo.setCurrentIndex(
+ readme_settings_async("roll_call_settings", "clear_record")
+ )
+ self.clear_record_combo.currentIndexChanged.connect(
+ lambda: update_settings(
+ "roll_call_settings",
+ "clear_record",
+ self.clear_record_combo.currentIndex(),
+ )
+ )
# 半重复抽取次数输入框
self.half_repeat_spin = SpinBox()
self.half_repeat_spin.setFixedWidth(WIDTH_SPINBOX)
self.half_repeat_spin.setRange(0, 100)
self.half_repeat_spin.setSuffix("次")
- self.half_repeat_spin.setValue(readme_settings_async("roll_call_settings", "half_repeat"))
- self.half_repeat_spin.valueChanged.connect(lambda: update_settings("roll_call_settings", "half_repeat", self.half_repeat_spin.value()))
-
- # 抽取后定时清除时间输入框
- self.clear_time_spin = SpinBox()
- self.clear_time_spin.setFixedWidth(WIDTH_SPINBOX)
- self.clear_time_spin.setRange(0, 25600)
- self.clear_time_spin.setSuffix("秒")
- self.clear_time_spin.setValue(readme_settings_async("roll_call_settings", "clear_time"))
- self.clear_time_spin.valueChanged.connect(lambda: update_settings("roll_call_settings", "clear_time", self.clear_time_spin.value()))
+ self.half_repeat_spin.setValue(
+ readme_settings_async("roll_call_settings", "half_repeat")
+ )
+ self.half_repeat_spin.valueChanged.connect(
+ lambda: update_settings(
+ "roll_call_settings", "half_repeat", self.half_repeat_spin.value()
+ )
+ )
# 抽取方式下拉框
self.draw_type_combo = ComboBox()
- self.draw_type_combo.addItems(get_content_combo_name_async("roll_call_settings", "draw_type"))
- self.draw_type_combo.setCurrentIndex(readme_settings_async("roll_call_settings", "draw_type"))
- self.draw_type_combo.currentIndexChanged.connect(lambda: update_settings("roll_call_settings", "draw_type", self.draw_type_combo.currentIndex()))
+ self.draw_type_combo.addItems(
+ get_content_combo_name_async("roll_call_settings", "draw_type")
+ )
+ self.draw_type_combo.setCurrentIndex(
+ readme_settings_async("roll_call_settings", "draw_type")
+ )
+ self.draw_type_combo.currentIndexChanged.connect(
+ lambda: update_settings(
+ "roll_call_settings", "draw_type", self.draw_type_combo.currentIndex()
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_document_bullet_list_cube_20_filled"),
- get_content_name_async("roll_call_settings", "draw_mode"), get_content_description_async("roll_call_settings", "draw_mode"), self.draw_mode_combo)
- self.addGroup(get_theme_icon("ic_fluent_text_clear_formatting_20_filled"),
- get_content_name_async("roll_call_settings", "clear_record"), get_content_description_async("roll_call_settings", "clear_record"), self.clear_record_combo)
- self.addGroup(get_theme_icon("ic_fluent_clipboard_bullet_list_20_filled"),
- get_content_name_async("roll_call_settings", "half_repeat"), get_content_description_async("roll_call_settings", "half_repeat"), self.half_repeat_spin)
- self.addGroup(get_theme_icon("ic_fluent_timer_off_20_filled"),
- get_content_name_async("roll_call_settings", "clear_time"), get_content_description_async("roll_call_settings", "clear_time"), self.clear_time_spin)
- self.addGroup(get_theme_icon("ic_fluent_drawer_add_20_filled"),
- get_content_name_async("roll_call_settings", "draw_type"), get_content_description_async("roll_call_settings", "draw_type"), self.draw_type_combo)
-
+ self.addGroup(
+ get_theme_icon("ic_fluent_document_bullet_list_cube_20_filled"),
+ get_content_name_async("roll_call_settings", "draw_mode"),
+ get_content_description_async("roll_call_settings", "draw_mode"),
+ self.draw_mode_combo,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_text_clear_formatting_20_filled"),
+ get_content_name_async("roll_call_settings", "clear_record"),
+ get_content_description_async("roll_call_settings", "clear_record"),
+ self.clear_record_combo,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_clipboard_bullet_list_20_filled"),
+ get_content_name_async("roll_call_settings", "half_repeat"),
+ get_content_description_async("roll_call_settings", "half_repeat"),
+ self.half_repeat_spin,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_drawer_add_20_filled"),
+ get_content_name_async("roll_call_settings", "draw_type"),
+ get_content_description_async("roll_call_settings", "draw_type"),
+ self.draw_type_combo,
+ )
+
# 初始化时调用一次,确保界面状态与设置一致
self.on_draw_mode_changed()
def on_draw_mode_changed(self):
"""当抽取模式改变时的处理逻辑"""
# 更新设置值
- update_settings("roll_call_settings", "draw_mode", self.draw_mode_combo.currentIndex())
-
+ update_settings(
+ "roll_call_settings", "draw_mode", self.draw_mode_combo.currentIndex()
+ )
+
# 获取当前抽取模式索引
draw_mode_index = self.draw_mode_combo.currentIndex()
-
+
# 根据抽取模式设置不同的控制逻辑
if draw_mode_index == 0: # 重复抽取模式
# 禁用清除抽取记录方式下拉框
self.clear_record_combo.setEnabled(False)
# 清空当前选项
self.clear_record_combo.clear()
- self.clear_record_combo.addItems(get_any_position_value_async("roll_call_settings", "clear_record", "combo_items_other"))
+ self.clear_record_combo.addItems(
+ get_any_position_value_async(
+ "roll_call_settings", "clear_record", "combo_items_other"
+ )
+ )
# 强制设置为"无需清除"(索引2)
self.clear_record_combo.setCurrentIndex(2)
# 更新设置
update_settings("roll_call_settings", "clear_record", 2)
-
+
# 设置half_repeat_spin为0并禁用
self.half_repeat_spin.setEnabled(False)
self.half_repeat_spin.setRange(0, 0)
self.half_repeat_spin.setValue(0)
# 更新设置
update_settings("roll_call_settings", "half_repeat", 0)
-
+
else: # 不重复抽取模式或半重复抽取模式
# 启用清除抽取记录方式下拉框
self.clear_record_combo.setEnabled(True)
-
+
# 清空当前选项
self.clear_record_combo.clear()
-
+
# 添加前两个选项(不包含"无需清除")
- self.clear_record_combo.addItems(get_content_combo_name_async("roll_call_settings", "clear_record"))
-
+ self.clear_record_combo.addItems(
+ get_content_combo_name_async("roll_call_settings", "clear_record")
+ )
+
# 设置默认选择第一个选项
self.clear_record_combo.setCurrentIndex(0)
# 更新设置
update_settings("roll_call_settings", "clear_record", 0)
-
+
# 根据具体模式设置half_repeat_spin
if draw_mode_index == 1: # 不重复抽取模式
# 设置half_repeat_spin为1并禁用
@@ -168,31 +210,69 @@ def __init__(self, parent=None):
# 字体大小输入框
self.font_size_spin = SpinBox()
- self.font_size_spin.setFixedWidth(WIDTH_SPINBOX)
+ self.font_size_spin.setFixedWidth(WIDTH_SPINBOX)
self.font_size_spin.setRange(10, 1000)
self.font_size_spin.setSuffix("px")
- self.font_size_spin.setValue(readme_settings_async("roll_call_settings", "font_size"))
- self.font_size_spin.valueChanged.connect(lambda: update_settings("roll_call_settings", "font_size", self.font_size_spin.value()))
+ self.font_size_spin.setValue(
+ readme_settings_async("roll_call_settings", "font_size")
+ )
+ self.font_size_spin.valueChanged.connect(
+ lambda: update_settings(
+ "roll_call_settings", "font_size", self.font_size_spin.value()
+ )
+ )
# 结果显示格式下拉框
self.display_format_combo = ComboBox()
- self.display_format_combo.addItems(get_content_combo_name_async("roll_call_settings", "display_format"))
- self.display_format_combo.setCurrentIndex(readme_settings_async("roll_call_settings", "display_format"))
- self.display_format_combo.currentIndexChanged.connect(lambda: update_settings("roll_call_settings", "display_format", self.display_format_combo.currentIndex()))
+ self.display_format_combo.addItems(
+ get_content_combo_name_async("roll_call_settings", "display_format")
+ )
+ self.display_format_combo.setCurrentIndex(
+ readme_settings_async("roll_call_settings", "display_format")
+ )
+ self.display_format_combo.currentIndexChanged.connect(
+ lambda: update_settings(
+ "roll_call_settings",
+ "display_format",
+ self.display_format_combo.currentIndex(),
+ )
+ )
# 显示随机组员格式下拉框
self.show_random_format_combo = ComboBox()
- self.show_random_format_combo.addItems(get_content_combo_name_async("roll_call_settings", "show_random"))
- self.show_random_format_combo.setCurrentIndex(readme_settings_async("roll_call_settings", "show_random"))
- self.show_random_format_combo.currentIndexChanged.connect(lambda: update_settings("roll_call_settings", "show_random", self.show_random_format_combo.currentIndex()))
+ self.show_random_format_combo.addItems(
+ get_content_combo_name_async("roll_call_settings", "show_random")
+ )
+ self.show_random_format_combo.setCurrentIndex(
+ readme_settings_async("roll_call_settings", "show_random")
+ )
+ self.show_random_format_combo.currentIndexChanged.connect(
+ lambda: update_settings(
+ "roll_call_settings",
+ "show_random",
+ self.show_random_format_combo.currentIndex(),
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_text_font_20_filled"),
- get_content_name_async("roll_call_settings", "font_size"), get_content_description_async("roll_call_settings", "font_size"), self.font_size_spin)
- self.addGroup(get_theme_icon("ic_fluent_slide_text_sparkle_20_filled"),
- get_content_name_async("roll_call_settings", "display_format"), get_content_description_async("roll_call_settings", "display_format"), self.display_format_combo)
- self.addGroup(get_theme_icon("ic_fluent_group_list_20_filled"),
- get_content_name_async("roll_call_settings", "show_random"), get_content_description_async("roll_call_settings", "show_random"), self.show_random_format_combo)
+ self.addGroup(
+ get_theme_icon("ic_fluent_text_font_20_filled"),
+ get_content_name_async("roll_call_settings", "font_size"),
+ get_content_description_async("roll_call_settings", "font_size"),
+ self.font_size_spin,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_slide_text_sparkle_20_filled"),
+ get_content_name_async("roll_call_settings", "display_format"),
+ get_content_description_async("roll_call_settings", "display_format"),
+ self.display_format_combo,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_group_list_20_filled"),
+ get_content_name_async("roll_call_settings", "show_random"),
+ get_content_description_async("roll_call_settings", "show_random"),
+ self.show_random_format_combo,
+ )
class roll_call_animation_settings(QWidget):
@@ -215,100 +295,197 @@ def __init__(self, parent=None):
self.student_image_widget = roll_call_student_image_settings(self)
self.vBoxLayout.addWidget(self.student_image_widget)
- # 添加音乐设置组件
- self.music_settings_widget = roll_call_music_settings(self)
- self.vBoxLayout.addWidget(self.music_settings_widget)
-
class roll_call_basic_animation_settings(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("roll_call_settings", "basic_animation_settings"))
+ self.setTitle(
+ get_content_name_async("roll_call_settings", "basic_animation_settings")
+ )
self.setBorderRadius(8)
# 动画模式下拉框
self.animation_combo = ComboBox()
- self.animation_combo.addItems(get_content_combo_name_async("roll_call_settings", "animation"))
- self.animation_combo.setCurrentIndex(readme_settings_async("roll_call_settings", "animation"))
- self.animation_combo.currentIndexChanged.connect(lambda: update_settings("roll_call_settings", "animation", self.animation_combo.currentIndex()))
+ self.animation_combo.addItems(
+ get_content_combo_name_async("roll_call_settings", "animation")
+ )
+ self.animation_combo.setCurrentIndex(
+ readme_settings_async("roll_call_settings", "animation")
+ )
+ self.animation_combo.currentIndexChanged.connect(
+ lambda: update_settings(
+ "roll_call_settings", "animation", self.animation_combo.currentIndex()
+ )
+ )
# 动画间隔输入框
self.animation_interval_spin = SpinBox()
self.animation_interval_spin.setFixedWidth(WIDTH_SPINBOX)
self.animation_interval_spin.setRange(1, 2000)
self.animation_interval_spin.setSuffix("ms")
- self.animation_interval_spin.setValue(readme_settings_async("roll_call_settings", "animation_interval"))
- self.animation_interval_spin.valueChanged.connect(lambda: update_settings("roll_call_settings", "animation_interval", self.animation_interval_spin.value()))
+ self.animation_interval_spin.setValue(
+ readme_settings_async("roll_call_settings", "animation_interval")
+ )
+ self.animation_interval_spin.valueChanged.connect(
+ lambda: update_settings(
+ "roll_call_settings",
+ "animation_interval",
+ self.animation_interval_spin.value(),
+ )
+ )
# 自动播放次数输入框
self.autoplay_count_spin = SpinBox()
self.autoplay_count_spin.setFixedWidth(WIDTH_SPINBOX)
self.autoplay_count_spin.setRange(0, 100)
self.autoplay_count_spin.setSuffix("次")
- self.autoplay_count_spin.setValue(readme_settings_async("roll_call_settings", "autoplay_count"))
- self.autoplay_count_spin.valueChanged.connect(lambda: update_settings("roll_call_settings", "autoplay_count", self.autoplay_count_spin.value()))
+ self.autoplay_count_spin.setValue(
+ readme_settings_async("roll_call_settings", "autoplay_count")
+ )
+ self.autoplay_count_spin.valueChanged.connect(
+ lambda: update_settings(
+ "roll_call_settings", "autoplay_count", self.autoplay_count_spin.value()
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_sanitize_20_filled"),
- get_content_name_async("roll_call_settings", "animation"), get_content_description_async("roll_call_settings", "animation"), self.animation_combo)
- self.addGroup(get_theme_icon("ic_fluent_timeline_20_filled"),
- get_content_name_async("roll_call_settings", "animation_interval"), get_content_description_async("roll_call_settings", "animation_interval"), self.animation_interval_spin)
- self.addGroup(get_theme_icon("ic_fluent_slide_play_20_filled"),
- get_content_name_async("roll_call_settings", "autoplay_count"), get_content_description_async("roll_call_settings", "autoplay_count"), self.autoplay_count_spin)
+ self.addGroup(
+ get_theme_icon("ic_fluent_sanitize_20_filled"),
+ get_content_name_async("roll_call_settings", "animation"),
+ get_content_description_async("roll_call_settings", "animation"),
+ self.animation_combo,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_timeline_20_filled"),
+ get_content_name_async("roll_call_settings", "animation_interval"),
+ get_content_description_async("roll_call_settings", "animation_interval"),
+ self.animation_interval_spin,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_slide_play_20_filled"),
+ get_content_name_async("roll_call_settings", "autoplay_count"),
+ get_content_description_async("roll_call_settings", "autoplay_count"),
+ self.autoplay_count_spin,
+ )
class roll_call_color_theme_settings(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("roll_call_settings", "color_theme_settings"))
+ self.setTitle(
+ get_content_name_async("roll_call_settings", "color_theme_settings")
+ )
self.setBorderRadius(8)
# 颜色主题下拉框
self.animation_color_theme_combo = ComboBox()
- self.animation_color_theme_combo.addItems(get_content_combo_name_async("roll_call_settings", "animation_color_theme"))
- self.animation_color_theme_combo.setCurrentIndex(readme_settings_async("roll_call_settings", "animation_color_theme"))
- self.animation_color_theme_combo.currentIndexChanged.connect(lambda: update_settings("roll_call_settings", "animation_color_theme", self.animation_color_theme_combo.currentIndex()))
+ self.animation_color_theme_combo.addItems(
+ get_content_combo_name_async("roll_call_settings", "animation_color_theme")
+ )
+ self.animation_color_theme_combo.setCurrentIndex(
+ readme_settings_async("roll_call_settings", "animation_color_theme")
+ )
+ self.animation_color_theme_combo.currentIndexChanged.connect(
+ lambda: update_settings(
+ "roll_call_settings",
+ "animation_color_theme",
+ self.animation_color_theme_combo.currentIndex(),
+ )
+ )
# 动画固定颜色
- self.animation_fixed_color_button = ColorConfigItem("roll_call_settings", "animation_fixed_color", readme_settings_async("roll_call_settings", "animation_fixed_color"))
- self.animation_fixed_color_button.valueChanged.connect(lambda color: update_settings("roll_call_settings", "animation_fixed_color", color.name()))
+ self.animation_fixed_color_button = ColorConfigItem(
+ "roll_call_settings",
+ "animation_fixed_color",
+ readme_settings_async("roll_call_settings", "animation_fixed_color"),
+ )
+ self.animation_fixed_color_button.valueChanged.connect(
+ lambda color: update_settings(
+ "roll_call_settings", "animation_fixed_color", color.name()
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_color_20_filled"),
- get_content_name_async("roll_call_settings", "animation_color_theme"), get_content_description_async("roll_call_settings", "animation_color_theme"), self.animation_color_theme_combo)
+ self.addGroup(
+ get_theme_icon("ic_fluent_color_20_filled"),
+ get_content_name_async("roll_call_settings", "animation_color_theme"),
+ get_content_description_async(
+ "roll_call_settings", "animation_color_theme"
+ ),
+ self.animation_color_theme_combo,
+ )
self.animationColorCard = ColorSettingCard(
self.animation_fixed_color_button,
get_theme_icon("ic_fluent_text_color_20_filled"),
- self.tr(get_content_name_async("roll_call_settings", "animation_fixed_color")),
- self.tr(get_content_description_async("roll_call_settings", "animation_fixed_color")),
- self
+ self.tr(
+ get_content_name_async("roll_call_settings", "animation_fixed_color")
+ ),
+ self.tr(
+ get_content_description_async(
+ "roll_call_settings", "animation_fixed_color"
+ )
+ ),
+ self,
)
self.vBoxLayout.addWidget(self.animationColorCard)
+
class roll_call_student_image_settings(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("roll_call_settings", "student_image_settings"))
+ self.setTitle(
+ get_content_name_async("roll_call_settings", "student_image_settings")
+ )
self.setBorderRadius(8)
# 学生图片开关
self.student_image_switch = SwitchButton()
- self.student_image_switch.setOffText(get_content_switchbutton_name_async("roll_call_settings", "student_image", "disable"))
- self.student_image_switch.setOnText(get_content_switchbutton_name_async("roll_call_settings", "student_image", "enable"))
- self.student_image_switch.setChecked(readme_settings_async("roll_call_settings", "student_image"))
- self.student_image_switch.checkedChanged.connect(lambda: update_settings("roll_call_settings", "student_image", self.student_image_switch.isChecked()))
+ self.student_image_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "roll_call_settings", "student_image", "disable"
+ )
+ )
+ self.student_image_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "roll_call_settings", "student_image", "enable"
+ )
+ )
+ self.student_image_switch.setChecked(
+ readme_settings_async("roll_call_settings", "student_image")
+ )
+ self.student_image_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "roll_call_settings",
+ "student_image",
+ self.student_image_switch.isChecked(),
+ )
+ )
# 打开学生图片文件夹按钮
- self.open_student_image_folder_button = PushButton(get_content_name_async("roll_call_settings", "open_student_image_folder"))
- self.open_student_image_folder_button.clicked.connect(lambda: self.open_student_image_folder())
+ self.open_student_image_folder_button = PushButton(
+ get_content_name_async("roll_call_settings", "open_student_image_folder")
+ )
+ self.open_student_image_folder_button.clicked.connect(
+ lambda: self.open_student_image_folder()
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_image_circle_20_filled"),
- get_content_name_async("roll_call_settings", "student_image"), get_content_description_async("roll_call_settings", "student_image"), self.student_image_switch)
- self.addGroup(get_theme_icon("ic_fluent_folder_open_20_filled"),
- get_content_name_async("roll_call_settings", "open_student_image_folder"), get_content_description_async("roll_call_settings", "open_student_image_folder"), self.open_student_image_folder_button)
+ self.addGroup(
+ get_theme_icon("ic_fluent_image_circle_20_filled"),
+ get_content_name_async("roll_call_settings", "student_image"),
+ get_content_description_async("roll_call_settings", "student_image"),
+ self.student_image_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_folder_open_20_filled"),
+ get_content_name_async("roll_call_settings", "open_student_image_folder"),
+ get_content_description_async(
+ "roll_call_settings", "open_student_image_folder"
+ ),
+ self.open_student_image_folder_button,
+ )
def open_student_image_folder(self):
"""打开学生图片文件夹"""
@@ -319,121 +496,3 @@ def open_student_image_folder(self):
QDesktopServices.openUrl(QUrl.fromLocalFile(str(folder_path)))
else:
logger.error("无法获取学生图片文件夹路径")
-
-class roll_call_music_settings(GroupHeaderCardWidget):
- def __init__(self, parent=None):
- super().__init__(parent)
- self.setTitle(get_content_name_async("roll_call_settings", "music_settings"))
- self.setBorderRadius(8)
-
- # 动画音乐开关
- self.animation_music_switch = SwitchButton()
- self.animation_music_switch.setOffText(get_content_switchbutton_name_async("roll_call_settings", "animation_music", "disable"))
- self.animation_music_switch.setOnText(get_content_switchbutton_name_async("roll_call_settings", "animation_music", "enable"))
- self.animation_music_switch.setChecked(readme_settings_async("roll_call_settings", "animation_music"))
- self.animation_music_switch.checkedChanged.connect(lambda: update_settings("roll_call_settings", "animation_music", self.animation_music_switch.isChecked()))
-
- # 结果音乐开关
- self.result_music_switch = SwitchButton()
- self.result_music_switch.setOffText(get_content_switchbutton_name_async("roll_call_settings", "result_music", "disable"))
- self.result_music_switch.setOnText(get_content_switchbutton_name_async("roll_call_settings", "result_music", "enable"))
- self.result_music_switch.setChecked(readme_settings_async("roll_call_settings", "result_music"))
- self.result_music_switch.checkedChanged.connect(lambda: update_settings("roll_call_settings", "result_music", self.result_music_switch.isChecked()))
-
- # 动画音乐文件夹按钮
- self.open_animation_music_folder_button = PushButton(get_content_name_async("roll_call_settings", "open_animation_music_folder"))
- self.open_animation_music_folder_button.clicked.connect(lambda: self.open_animation_music_folder())
-
- # 结果音乐文件夹按钮
- self.open_result_music_folder_button = PushButton(get_content_name_async("roll_call_settings", "open_result_music_folder"))
- self.open_result_music_folder_button.clicked.connect(lambda: self.open_result_music_folder())
-
- # 动画音乐音量
- self.animation_music_volume_spin = SpinBox()
- self.animation_music_volume_spin.setFixedWidth(WIDTH_SPINBOX)
- self.animation_music_volume_spin.setRange(0, 100)
- self.animation_music_volume_spin.setSuffix("%")
- self.animation_music_volume_spin.setValue(readme_settings_async("roll_call_settings", "animation_music_volume"))
- self.animation_music_volume_spin.valueChanged.connect(lambda: update_settings("roll_call_settings", "animation_music_volume", self.animation_music_volume_spin.value()))
-
- # 结果音乐音量
- self.result_music_volume_spin = SpinBox()
- self.result_music_volume_spin.setFixedWidth(WIDTH_SPINBOX)
- self.result_music_volume_spin.setRange(0, 100)
- self.result_music_volume_spin.setSuffix("%")
- self.result_music_volume_spin.setValue(readme_settings_async("roll_call_settings", "result_music_volume"))
- self.result_music_volume_spin.valueChanged.connect(lambda: update_settings("roll_call_settings", "result_music_volume", self.result_music_volume_spin.value()))
-
- # 动画音乐淡入时间
- self.animation_music_fade_in_spin = SpinBox()
- self.animation_music_fade_in_spin.setFixedWidth(WIDTH_SPINBOX)
- self.animation_music_fade_in_spin.setRange(0, 1000)
- self.animation_music_fade_in_spin.setSuffix("ms")
- self.animation_music_fade_in_spin.setValue(readme_settings_async("roll_call_settings", "animation_music_fade_in"))
- self.animation_music_fade_in_spin.valueChanged.connect(lambda: update_settings("roll_call_settings", "animation_music_fade_in", self.animation_music_fade_in_spin.value()))
-
- # 结果音乐淡入时间
- self.result_music_fade_in_spin = SpinBox()
- self.result_music_fade_in_spin.setFixedWidth(WIDTH_SPINBOX)
- self.result_music_fade_in_spin.setRange(0, 1000)
- self.result_music_fade_in_spin.setSuffix("ms")
- self.result_music_fade_in_spin.setValue(readme_settings_async("roll_call_settings", "result_music_fade_in"))
- self.result_music_fade_in_spin.valueChanged.connect(lambda: update_settings("roll_call_settings", "result_music_fade_in", self.result_music_fade_in_spin.value()))
-
- # 动画音乐淡出时间
- self.animation_music_fade_out_spin = SpinBox()
- self.animation_music_fade_out_spin.setFixedWidth(WIDTH_SPINBOX)
- self.animation_music_fade_out_spin.setRange(0, 1000)
- self.animation_music_fade_out_spin.setSuffix("ms")
- self.animation_music_fade_out_spin.setValue(readme_settings_async("roll_call_settings", "animation_music_fade_out"))
- self.animation_music_fade_out_spin.valueChanged.connect(lambda: update_settings("roll_call_settings", "animation_music_fade_out", self.animation_music_fade_out_spin.value()))
-
- # 结果音乐淡出时间
- self.result_music_fade_out_spin = SpinBox()
- self.result_music_fade_out_spin.setFixedWidth(WIDTH_SPINBOX)
- self.result_music_fade_out_spin.setRange(0, 1000)
- self.result_music_fade_out_spin.setSuffix("ms")
- self.result_music_fade_out_spin.setValue(readme_settings_async("roll_call_settings", "result_music_fade_out"))
- self.result_music_fade_out_spin.valueChanged.connect(lambda: update_settings("roll_call_settings", "result_music_fade_out", self.result_music_fade_out_spin.value()))
-
- # 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_music_note_2_20_filled"),
- get_content_name_async("roll_call_settings", "animation_music"), get_content_description_async("roll_call_settings", "animation_music"), self.animation_music_switch)
- self.addGroup(get_theme_icon("ic_fluent_music_note_2_20_filled"),
- get_content_name_async("roll_call_settings", "result_music"), get_content_description_async("roll_call_settings", "result_music"), self.result_music_switch)
- self.addGroup(get_theme_icon("ic_fluent_folder_open_20_filled"),
- get_content_name_async("roll_call_settings", "open_animation_music_folder"), get_content_description_async("roll_call_settings", "open_animation_music_folder"), self.open_animation_music_folder_button)
- self.addGroup(get_theme_icon("ic_fluent_folder_open_20_filled"),
- get_content_name_async("roll_call_settings", "open_result_music_folder"), get_content_description_async("roll_call_settings", "open_result_music_folder"), self.open_result_music_folder_button)
- self.addGroup(get_theme_icon("ic_fluent_speaker_2_20_filled"),
- get_content_name_async("roll_call_settings", "animation_music_volume"), get_content_description_async("roll_call_settings", "animation_music_volume"), self.animation_music_volume_spin)
- self.addGroup(get_theme_icon("ic_fluent_speaker_2_20_filled"),
- get_content_name_async("roll_call_settings", "result_music_volume"), get_content_description_async("roll_call_settings", "result_music_volume"), self.result_music_volume_spin)
- self.addGroup(get_theme_icon("ic_fluent_arrow_up_20_filled"),
- get_content_name_async("roll_call_settings", "animation_music_fade_in"), get_content_description_async("roll_call_settings", "animation_music_fade_in"), self.animation_music_fade_in_spin)
- self.addGroup(get_theme_icon("ic_fluent_arrow_up_20_filled"),
- get_content_name_async("roll_call_settings", "result_music_fade_in"), get_content_description_async("roll_call_settings", "result_music_fade_in"), self.result_music_fade_in_spin)
- self.addGroup(get_theme_icon("ic_fluent_arrow_down_20_filled"),
- get_content_name_async("roll_call_settings", "animation_music_fade_out"), get_content_description_async("roll_call_settings", "animation_music_fade_out"), self.animation_music_fade_out_spin)
- self.addGroup(get_theme_icon("ic_fluent_arrow_down_20_filled"),
- get_content_name_async("roll_call_settings", "result_music_fade_out"), get_content_description_async("roll_call_settings", "result_music_fade_out"), self.result_music_fade_out_spin)
-
- def open_animation_music_folder(self):
- """打开动画音乐文件夹"""
- folder_path = get_resources_path(ANIMATION_MUSIC_FOLDER)
- if not folder_path.exists():
- os.makedirs(folder_path)
- if folder_path:
- QDesktopServices.openUrl(QUrl.fromLocalFile(str(folder_path)))
- else:
- logger.error("无法获取动画音乐文件夹路径")
-
- def open_result_music_folder(self):
- """打开结果音乐文件夹"""
- folder_path = get_resources_path(RESULT_MUSIC_FOLDER)
- if not folder_path.exists():
- os.makedirs(folder_path)
- if folder_path:
- QDesktopServices.openUrl(QUrl.fromLocalFile(str(folder_path)))
- else:
- logger.error("无法获取结果音乐文件夹路径")
\ No newline at end of file
diff --git a/app/view/settings/history/__init__.py b/app/view/settings/history/__init__.py
new file mode 100644
index 00000000..cbebba5c
--- /dev/null
+++ b/app/view/settings/history/__init__.py
@@ -0,0 +1 @@
+"""History settings pages."""
diff --git a/app/view/settings/history/history_management.py b/app/view/settings/history/history_management.py
index 68224081..ccc4e781 100644
--- a/app/view/settings/history/history_management.py
+++ b/app/view/settings/history/history_management.py
@@ -1,16 +1,12 @@
# ==================================================
# 导入库
# ==================================================
-import json
-import os
-import sys
-import subprocess
from loguru import logger
-from PyQt6.QtWidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from PySide6.QtNetwork import *
from qfluentwidgets import *
from app.tools.variable import *
@@ -21,6 +17,7 @@
from app.Language.obtain_language import *
from app.tools.list import *
+
# ==================================================
# 历史记录管理
# ==================================================
@@ -40,6 +37,7 @@ def __init__(self, parent=None):
self.lottery_history = lottery_history(self)
self.vBoxLayout.addWidget(self.lottery_history)
+
class roll_call_history(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
@@ -48,31 +46,79 @@ def __init__(self, parent=None):
# 是否开启点名历史记录
self.show_roll_call_history_button_switch = SwitchButton()
- self.show_roll_call_history_button_switch.setOffText(get_content_switchbutton_name_async("history_management", "show_roll_call_history", "disable"))
- self.show_roll_call_history_button_switch.setOnText(get_content_switchbutton_name_async("history_management", "show_roll_call_history", "enable"))
- self.show_roll_call_history_button_switch.setChecked(readme_settings_async("history_management", "show_roll_call_history"))
- self.show_roll_call_history_button_switch.checkedChanged.connect(lambda: update_settings("history_management", "show_roll_call_history", self.show_roll_call_history_button_switch.isChecked()))
+ self.show_roll_call_history_button_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "history_management", "show_roll_call_history", "disable"
+ )
+ )
+ self.show_roll_call_history_button_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "history_management", "show_roll_call_history", "enable"
+ )
+ )
+ self.show_roll_call_history_button_switch.setChecked(
+ readme_settings_async("history_management", "show_roll_call_history")
+ )
+ self.show_roll_call_history_button_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "history_management",
+ "show_roll_call_history",
+ self.show_roll_call_history_button_switch.isChecked(),
+ )
+ )
# 选择班级下拉框
self.class_name_combo = ComboBox()
self.refresh_class_list() # 初始化班级列表
- self.class_name_combo.setCurrentIndex(readme_settings_async("history_management", "select_class_name"))
+ self.class_name_combo.setCurrentIndex(
+ readme_settings_async("history_management", "select_class_name")
+ )
if not get_class_name_list():
self.class_name_combo.setCurrentIndex(-1)
- self.class_name_combo.setPlaceholderText(get_content_name_async("history_management", "select_class_name"))
- self.class_name_combo.currentIndexChanged.connect(lambda: update_settings("history_management", "select_class_name", self.class_name_combo.currentIndex()))
+ self.class_name_combo.setPlaceholderText(
+ get_content_name_async("history_management", "select_class_name")
+ )
+ self.class_name_combo.currentIndexChanged.connect(
+ lambda: update_settings(
+ "history_management",
+ "select_class_name",
+ self.class_name_combo.currentIndex(),
+ )
+ )
# 清除历史记录按钮
- self.clear_roll_call_history_button = PushButton(get_content_pushbutton_name_async("history_management", "clear_roll_call_history"))
- self.clear_roll_call_history_button.clicked.connect(lambda: self.clear_roll_call_history)
+ self.clear_roll_call_history_button = PushButton(
+ get_content_pushbutton_name_async(
+ "history_management", "clear_roll_call_history"
+ )
+ )
+ self.clear_roll_call_history_button.clicked.connect(
+ lambda: self.clear_roll_call_history
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_history_20_filled"),
- get_content_name_async("history_management", "show_roll_call_history"), get_content_description_async("history_management", "show_roll_call_history"), self.show_roll_call_history_button_switch)
- self.addGroup(get_theme_icon("ic_fluent_class_20_filled"),
- get_content_name_async("history_management", "select_class_name"), get_content_description_async("history_management", "select_class_name"), self.class_name_combo)
- self.addGroup(get_theme_icon("ic_fluent_people_community_20_filled"),
- get_content_name_async("history_management", "clear_roll_call_history"), get_content_description_async("history_management", "clear_roll_call_history"), self.clear_roll_call_history_button)
+ self.addGroup(
+ get_theme_icon("ic_fluent_history_20_filled"),
+ get_content_name_async("history_management", "show_roll_call_history"),
+ get_content_description_async(
+ "history_management", "show_roll_call_history"
+ ),
+ self.show_roll_call_history_button_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_class_20_filled"),
+ get_content_name_async("history_management", "select_class_name"),
+ get_content_description_async("history_management", "select_class_name"),
+ self.class_name_combo,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_people_community_20_filled"),
+ get_content_name_async("history_management", "clear_roll_call_history"),
+ get_content_description_async(
+ "history_management", "clear_roll_call_history"
+ ),
+ self.clear_roll_call_history_button,
+ )
# 设置文件系统监视器
self.setup_file_watcher()
@@ -85,25 +131,25 @@ def setup_file_watcher(self):
"""设置文件系统监视器,监控班级名单文件夹的变化"""
# 获取班级名单文件夹路径
roll_call_list_dir = get_path("app/resources/list/roll_call_list")
-
+
# 确保目录存在
if not roll_call_list_dir.exists():
logger.warning(f"班级名单文件夹不存在: {roll_call_list_dir}")
return
-
+
# 创建文件系统监视器
self.file_watcher = QFileSystemWatcher()
-
+
# 监视目录
self.file_watcher.addPath(str(roll_call_list_dir))
-
+
# 连接信号
self.file_watcher.directoryChanged.connect(self.on_directory_changed)
# logger.debug(f"已设置文件监视器,监控目录: {roll_call_list_dir}")
def on_directory_changed(self, path):
"""当目录内容发生变化时调用此方法
-
+
Args:
path: 发生变化的目录路径
"""
@@ -115,24 +161,27 @@ def refresh_class_list(self):
"""刷新班级下拉框列表"""
# 保存当前选中的班级名称
current_class_name = self.class_name_combo.currentText()
-
+
# 获取最新的班级列表
class_list = get_class_name_list()
-
+
# 清空并重新添加班级列表
self.class_name_combo.clear()
self.class_name_combo.addItems(class_list)
-
+
# 尝试恢复之前选中的班级
if current_class_name and current_class_name in class_list:
index = class_list.index(current_class_name)
self.class_name_combo.setCurrentIndex(index)
elif not class_list:
self.class_name_combo.setCurrentIndex(-1)
- self.class_name_combo.setPlaceholderText(get_content_name_async("roll_call_list", "select_class_name"))
-
+ self.class_name_combo.setPlaceholderText(
+ get_content_name_async("roll_call_list", "select_class_name")
+ )
+
# logger.debug(f"班级列表已刷新,共 {len(class_list)} 个班级")
+
class lottery_history(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
@@ -141,31 +190,75 @@ def __init__(self, parent=None):
# 是否开启抽奖历史记录
self.show_lottery_history_button_switch = SwitchButton()
- self.show_lottery_history_button_switch.setOffText(get_content_switchbutton_name_async("history_management", "show_lottery_history", "disable"))
- self.show_lottery_history_button_switch.setOnText(get_content_switchbutton_name_async("history_management", "show_lottery_history", "enable"))
- self.show_lottery_history_button_switch.setChecked(readme_settings_async("history_management", "show_lottery_history"))
- self.show_lottery_history_button_switch.checkedChanged.connect(lambda: update_settings("history_management", "show_lottery_history", self.show_lottery_history_button_switch.isChecked()))
+ self.show_lottery_history_button_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "history_management", "show_lottery_history", "disable"
+ )
+ )
+ self.show_lottery_history_button_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "history_management", "show_lottery_history", "enable"
+ )
+ )
+ self.show_lottery_history_button_switch.setChecked(
+ readme_settings_async("history_management", "show_lottery_history")
+ )
+ self.show_lottery_history_button_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "history_management",
+ "show_lottery_history",
+ self.show_lottery_history_button_switch.isChecked(),
+ )
+ )
# 选择奖池下拉框
self.pool_name_combo = ComboBox()
self.refresh_pool_list() # 初始化奖池列表
- self.pool_name_combo.setCurrentIndex(readme_settings_async("history_management", "select_pool_name"))
+ self.pool_name_combo.setCurrentIndex(
+ readme_settings_async("history_management", "select_pool_name")
+ )
if not get_pool_name_list():
self.pool_name_combo.setCurrentIndex(-1)
- self.pool_name_combo.setPlaceholderText(get_content_name_async("history_management", "select_pool_name"))
- self.pool_name_combo.currentIndexChanged.connect(lambda: update_settings("history_management", "select_pool_name", self.pool_name_combo.currentIndex()))
+ self.pool_name_combo.setPlaceholderText(
+ get_content_name_async("history_management", "select_pool_name")
+ )
+ self.pool_name_combo.currentIndexChanged.connect(
+ lambda: update_settings(
+ "history_management",
+ "select_pool_name",
+ self.pool_name_combo.currentIndex(),
+ )
+ )
# 清除历史记录按钮
- self.clear_lottery_history_button = PushButton(get_content_pushbutton_name_async("history_management", "clear_lottery_history"))
+ self.clear_lottery_history_button = PushButton(
+ get_content_pushbutton_name_async(
+ "history_management", "clear_lottery_history"
+ )
+ )
self.clear_lottery_history_button.clicked.connect(self.clear_lottery_history)
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_history_20_filled"),
- get_content_name_async("history_management", "show_lottery_history"), get_content_description_async("history_management", "show_lottery_history"), self.show_lottery_history_button_switch)
- self.addGroup(get_theme_icon("ic_fluent_class_20_filled"),
- get_content_name_async("history_management", "select_pool_name"), get_content_description_async("history_management", "select_pool_name"), self.pool_name_combo)
- self.addGroup(get_theme_icon("ic_fluent_people_community_20_filled"),
- get_content_name_async("history_management", "clear_lottery_history"), get_content_description_async("history_management", "clear_lottery_history"), self.clear_lottery_history_button)
+ self.addGroup(
+ get_theme_icon("ic_fluent_history_20_filled"),
+ get_content_name_async("history_management", "show_lottery_history"),
+ get_content_description_async("history_management", "show_lottery_history"),
+ self.show_lottery_history_button_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_class_20_filled"),
+ get_content_name_async("history_management", "select_pool_name"),
+ get_content_description_async("history_management", "select_pool_name"),
+ self.pool_name_combo,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_people_community_20_filled"),
+ get_content_name_async("history_management", "clear_lottery_history"),
+ get_content_description_async(
+ "history_management", "clear_lottery_history"
+ ),
+ self.clear_lottery_history_button,
+ )
# 设置文件系统监视器
self.setup_file_watcher()
@@ -178,25 +271,25 @@ def setup_file_watcher(self):
"""设置文件系统监视器,监控奖池名单文件夹的变化"""
# 获取奖池名单文件夹路径
lottery_list_dir = get_path("app/resources/list/lottery_list")
-
+
# 确保目录存在
if not lottery_list_dir.exists():
logger.warning(f"奖池名单文件夹不存在: {lottery_list_dir}")
return
-
+
# 创建文件系统监视器
self.file_watcher = QFileSystemWatcher()
-
+
# 监视目录
self.file_watcher.addPath(str(lottery_list_dir))
-
+
# 连接信号
self.file_watcher.directoryChanged.connect(self.on_directory_changed)
# logger.debug(f"已设置文件监视器,监控目录: {lottery_list_dir}")
def on_directory_changed(self, path):
"""当目录内容发生变化时调用此方法
-
+
Args:
path: 发生变化的目录路径
"""
@@ -208,20 +301,22 @@ def refresh_pool_list(self):
"""刷新奖池下拉框列表"""
# 保存当前选中的奖池名称
current_pool_name = self.pool_name_combo.currentText()
-
+
# 获取最新的奖池列表
pool_list = get_pool_name_list()
-
+
# 清空并重新添加奖池列表
self.pool_name_combo.clear()
self.pool_name_combo.addItems(pool_list)
-
+
# 尝试恢复之前选中的奖池
if current_pool_name and current_pool_name in pool_list:
index = pool_list.index(current_pool_name)
self.pool_name_combo.setCurrentIndex(index)
elif not pool_list:
self.pool_name_combo.setCurrentIndex(-1)
- self.pool_name_combo.setPlaceholderText(get_content_name_async("lottery_list", "select_pool_name"))
-
+ self.pool_name_combo.setPlaceholderText(
+ get_content_name_async("lottery_list", "select_pool_name")
+ )
+
# logger.debug(f"奖池列表已刷新,共 {len(pool_list)} 个奖池")
diff --git a/app/view/settings/history/lottery_history_table.py b/app/view/settings/history/lottery_history_table.py
index 722319ab..4b17ae5c 100644
--- a/app/view/settings/history/lottery_history_table.py
+++ b/app/view/settings/history/lottery_history_table.py
@@ -2,18 +2,12 @@
# 导入库
# ==================================================
import json
-import os
-import sys
-import subprocess
-import pandas as pd
-from collections import OrderedDict
-from datetime import datetime
from loguru import logger
-from PyQt6.QtWidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from PySide6.QtNetwork import *
from qfluentwidgets import *
from app.tools.variable import *
@@ -28,78 +22,94 @@
# 点名历史记录表格
# ==================================================
+
class lottery_history_table(GroupHeaderCardWidget):
"""点名历史记录表格卡片"""
-
- refresh_signal = pyqtSignal()
-
+
+ refresh_signal = Signal()
+
def __init__(self, parent=None):
super().__init__(parent)
self.parent = parent
self.setTitle(get_content_name_async("lottery_history_table", "title"))
self.setBorderRadius(8)
-
+
# 初始化数据加载器
pool_history = get_all_history_names("lottery")
self.data_loader = None
- self.current_pool_name = pool_history[0] if pool_history else ''
+ self.current_pool_name = pool_history[0] if pool_history else ""
self.current_mode = 0
self.batch_size = 30 # 每次加载的行数
self.current_row = 0 # 当前加载到的行数
- self.total_rows = 0 # 总行数
+ self.total_rows = 0 # 总行数
self.is_loading = False # 是否正在加载数据
-
+
# 创建奖池选择区域
QTimer.singleShot(APPLY_DELAY, self.create_pool_selection)
-
+
# 创建表格区域
QTimer.singleShot(APPLY_DELAY, self.create_table)
-
+
# 初始化奖池列表
QTimer.singleShot(APPLY_DELAY, self.refresh_pool_history)
-
+
# 设置文件系统监视器
QTimer.singleShot(APPLY_DELAY, self.setup_file_watcher)
-
+
# 初始化数据
QTimer.singleShot(APPLY_DELAY, self.refresh_data)
-
+
# 连接信号
self.refresh_signal.connect(self.refresh_data)
-
+
def create_pool_selection(self):
"""创建奖池选择区域"""
self.pool_comboBox = ComboBox()
-
+
# 获取奖池历史列表并填充下拉框
pool_history = get_all_history_names("lottery")
self.pool_comboBox.addItems(pool_history)
-
+
# 设置默认选择
if pool_history:
- saved_index = readme_settings_async("lottery_history_table", "select_pool_name")
+ saved_index = readme_settings_async(
+ "lottery_history_table", "select_pool_name"
+ )
self.pool_comboBox.setCurrentIndex(0)
self.current_pool_name = pool_history[0]
else:
# 如果没有奖池历史,设置占位符
self.pool_comboBox.setCurrentIndex(-1)
- self.pool_comboBox.setPlaceholderText(get_content_name_async("lottery_history_table", "select_pool_name"))
+ self.pool_comboBox.setPlaceholderText(
+ get_content_name_async("lottery_history_table", "select_pool_name")
+ )
self.current_pool_name = ""
-
+
self.pool_comboBox.currentIndexChanged.connect(self.on_pool_changed)
self.pool_comboBox.currentTextChanged.connect(lambda: self.on_pool_changed(-1))
# 选择查看模式
self.all_names = get_all_names("lottery", self.pool_comboBox.currentText())
self.mode_comboBox = ComboBox()
- self.mode_comboBox.addItems(get_content_combo_name_async("lottery_history_table", "select_mode") + self.all_names)
+ self.mode_comboBox.addItems(
+ get_content_combo_name_async("lottery_history_table", "select_mode")
+ + self.all_names
+ )
self.mode_comboBox.setCurrentIndex(0)
self.mode_comboBox.currentIndexChanged.connect(self.refresh_data)
- self.addGroup(get_theme_icon("ic_fluent_class_20_filled"),
- get_content_name_async("lottery_history_table", "select_pool_name"), get_content_description_async("lottery_history_table", "select_pool_name"), self.pool_comboBox)
- self.addGroup(get_theme_icon("ic_fluent_reading_mode_mobile_20_filled"),
- get_content_name_async("lottery_history_table", "select_mode"), get_content_description_async("lottery_history_table", "select_mode"), self.mode_comboBox)
+ self.addGroup(
+ get_theme_icon("ic_fluent_class_20_filled"),
+ get_content_name_async("lottery_history_table", "select_pool_name"),
+ get_content_description_async("lottery_history_table", "select_pool_name"),
+ self.pool_comboBox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_reading_mode_mobile_20_filled"),
+ get_content_name_async("lottery_history_table", "select_mode"),
+ get_content_description_async("lottery_history_table", "select_mode"),
+ self.mode_comboBox,
+ )
def create_table(self):
"""创建表格区域"""
@@ -114,64 +124,70 @@ def create_table(self):
self.table.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
self.table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
self.table.verticalHeader().hide()
-
+
# 初始化排序状态
self.sort_column = -1
self.sort_order = Qt.SortOrder.AscendingOrder
# 根据当前选择的模式设置表格头
self.update_table_headers()
-
+
# 设置表格属性
for i in range(self.table.columnCount()):
- self.table.horizontalHeader().setSectionResizeMode(i, QHeaderView.ResizeMode.Stretch)
- self.table.horizontalHeader().setDefaultAlignment(Qt.AlignmentFlag.AlignCenter)
+ self.table.horizontalHeader().setSectionResizeMode(
+ i, QHeaderView.ResizeMode.Stretch
+ )
+ self.table.horizontalHeader().setDefaultAlignment(
+ Qt.AlignmentFlag.AlignCenter
+ )
self.table.horizontalHeader().setSectionsClickable(True)
-
+
# 初始状态下不显示排序指示器
self.table.horizontalHeader().setSortIndicatorShown(False)
-
+
# 连接滚动事件,用于分段加载
self.table.verticalScrollBar().valueChanged.connect(self._on_scroll)
-
+
# 连接排序信号,在排序时重新加载数据
self.table.horizontalHeader().sectionClicked.connect(self._on_header_clicked)
-
+
self.layout().addWidget(self.table)
-
+
def _on_scroll(self, value):
"""处理表格滚动事件,实现分段加载
-
+
Args:
value: 滚动条当前位置
"""
# 如果正在加载或没有更多数据,直接返回
if self.is_loading or self.current_row >= self.total_rows:
return
-
+
# 获取滚动条最大值和当前值
max_value = self.table.verticalScrollBar().maximum()
current_value = self.table.verticalScrollBar().value()
-
+
# 使用更精确的滚动检测,确保在滚动到底部时触发
scroll_threshold = max(20, max_value * 0.1) # 至少20像素或10%的位置
if current_value >= max_value - scroll_threshold:
self._load_more_data()
-
+
def _on_header_clicked(self, column):
"""处理表头点击事件,实现排序
-
+
Args:
column: 被点击的列索引
"""
# 如果正在加载数据,不处理排序
if self.is_loading:
return
-
+
# 获取当前排序状态,优先使用我们自己的状态变量
current_sort_column = self.sort_column if self.sort_column >= 0 else -1
- current_sort_order = self.sort_order if self.sort_column >= 0 else Qt.SortOrder.AscendingOrder
-
+ current_sort_order = (
+ self.sort_order if self.sort_column >= 0 else Qt.SortOrder.AscendingOrder
+ )
+
# 如果点击的是同一列,则切换排序顺序;否则设置为升序
if column == current_sort_column:
# 切换排序顺序
@@ -182,22 +198,22 @@ def _on_header_clicked(self, column):
else:
# 点击不同列,设置为升序
new_sort_order = Qt.SortOrder.AscendingOrder
-
+
# 更新排序状态
self.sort_column = column
self.sort_order = new_sort_order
-
+
# 设置排序指示器
self.table.horizontalHeader().setSortIndicator(column, new_sort_order)
self.table.horizontalHeader().setSortIndicatorShown(True)
-
+
# 重置数据加载状态
self.current_row = 0
self.table.setRowCount(0)
-
+
# 重新加载数据
self.refresh_data()
-
+
def _sort_current_data(self):
"""对已加载的数据进行排序,不重新加载数据"""
# 获取当前表格中的所有数据
@@ -211,11 +227,11 @@ def _sort_current_data(self):
else:
row_data.append("")
table_data.append(row_data)
-
+
# 如果没有数据,直接返回
if not table_data:
return
-
+
# 根据排序状态对数据进行排序
def sort_key(row):
# 尝试将数据转换为数字,如果失败则使用字符串比较
@@ -225,7 +241,7 @@ def sort_key(row):
# 权重列可能包含非数字字符,尝试提取数字部分
weight_str = row[self.sort_column]
# 移除可能的前导零
- weight_str = weight_str.lstrip('0')
+ weight_str = weight_str.lstrip("0")
if not weight_str:
return 0.0
return float(weight_str)
@@ -233,367 +249,421 @@ def sort_key(row):
return float(row[self.sort_column])
except (ValueError, IndexError):
return row[self.sort_column]
-
+
# 应用排序
- reverse_order = (self.sort_order == Qt.SortOrder.DescendingOrder)
+ reverse_order = self.sort_order == Qt.SortOrder.DescendingOrder
table_data.sort(key=sort_key, reverse=reverse_order)
-
+
# 清空表格
self.table.setRowCount(0)
-
+
# 重新填充排序后的数据
self.table.setRowCount(len(table_data))
for row_idx, row_data in enumerate(table_data):
for col_idx, cell_data in enumerate(row_data):
item = create_table_item(cell_data)
self.table.setItem(row_idx, col_idx, item)
-
+
def _load_more_data(self):
"""加载更多数据"""
if self.is_loading or self.current_row >= self.total_rows:
return
-
+
self.is_loading = True
-
+
# 计算新的行数
new_row_count = min(self.current_row + self.batch_size, self.total_rows)
-
+
# 增加表格行数
self.table.setRowCount(new_row_count)
-
+
# 根据当前模式加载数据,与refresh_data方法保持一致
- if hasattr(self, 'mode_comboBox'):
+ if hasattr(self, "mode_comboBox"):
self.current_mode = self.mode_comboBox.currentIndex()
else:
self.current_mode = 0
-
+
if self.current_mode == 0:
self._load_more_lotterys_data()
elif self.current_mode == 1:
self._load_more_sessions_data()
else:
# 当模式值大于等于2时,表示选择了特定的奖品名称
- if hasattr(self, 'mode_comboBox'):
+ if hasattr(self, "mode_comboBox"):
self.current_lottery_name = self.mode_comboBox.currentText()
else:
# 如果没有mode_comboBox,从设置中获取奖品名称
- self.current_lottery_name = readme_settings_async("lottery_history_table", "select_lottery_name")
+ self.current_lottery_name = readme_settings_async(
+ "lottery_history_table", "select_lottery_name"
+ )
self._load_more_stats_data(self.current_lottery_name)
-
+
# 数据加载完成后启用排序
if self.current_row >= self.total_rows:
self.table.setSortingEnabled(True)
if self.sort_column >= 0:
- self.table.horizontalHeader().setSortIndicator(self.sort_column, self.sort_order)
+ self.table.horizontalHeader().setSortIndicator(
+ self.sort_column, self.sort_order
+ )
self.table.horizontalHeader().setSortIndicatorShown(True)
-
+
self.is_loading = False
-
+
def _load_more_lotterys_data(self):
"""加载更多奖品数据"""
if not self.current_pool_name:
return
try:
- lottery_file = get_resources_path('list/lottery_list', f'{self.current_pool_name}.json')
- history_file = get_resources_path('history/lottery_history', f'{self.current_pool_name}.json')
- with open_file(lottery_file, 'r', encoding='utf-8') as f:
+ lottery_file = get_resources_path(
+ "list/lottery_list", f"{self.current_pool_name}.json"
+ )
+ history_file = get_resources_path(
+ "history/lottery_history", f"{self.current_pool_name}.json"
+ )
+ with open_file(lottery_file, "r", encoding="utf-8") as f:
pool_data = json.load(f)
cleaned_lotterys = []
for name, info in pool_data.items():
- if isinstance(info, dict) and info.get('exist', True):
- cleaned_lotterys.append((
- info.get('id', ''),
- name,
- info.get('weight', ''),
- ))
-
+ if isinstance(info, dict) and info.get("exist", True):
+ cleaned_lotterys.append(
+ (
+ info.get("id", ""),
+ name,
+ info.get("weight", ""),
+ )
+ )
+
history_data = {}
if file_exists(history_file):
try:
- with open_file(history_file, 'r', encoding='utf-8') as f:
+ with open_file(history_file, "r", encoding="utf-8") as f:
history_data = json.load(f)
except json.JSONDecodeError:
pass
-
- max_id_length = max(len(str(lottery[0])) for lottery in cleaned_lotterys) if cleaned_lotterys else 0
- max_total_count_length = max(len(str(history_data.get('lotterys', {}).get(name, {}).get('total_count', 0))) for _, name, _ in cleaned_lotterys) if cleaned_lotterys else 0
- max_weight_length = max(len(str(lottery[2])) for lottery in cleaned_lotterys) if cleaned_lotterys else 0
+
+ max_id_length = (
+ max(len(str(lottery[0])) for lottery in cleaned_lotterys)
+ if cleaned_lotterys
+ else 0
+ )
+ max_total_count_length = (
+ max(
+ len(
+ str(
+ history_data.get("lotterys", {})
+ .get(name, {})
+ .get("total_count", 0)
+ )
+ )
+ for _, name, _ in cleaned_lotterys
+ )
+ if cleaned_lotterys
+ else 0
+ )
lotterys_data = []
for lottery_id, name, weight in cleaned_lotterys:
- total_count = int(history_data.get('lotterys', {}).get(name, {}).get('total_count', 0))
- lotterys_data.append({
- 'id': str(lottery_id).zfill(max_id_length),
- 'name': name,
- 'weight': weight,
- 'total_count': total_count,
- 'total_count_str': str(total_count).zfill(max_total_count_length)
- })
-
+ total_count = int(
+ history_data.get("lotterys", {}).get(name, {}).get("total_count", 0)
+ )
+ lotterys_data.append(
+ {
+ "id": str(lottery_id).zfill(max_id_length),
+ "name": name,
+ "weight": weight,
+ "total_count": total_count,
+ "total_count_str": str(total_count).zfill(
+ max_total_count_length
+ ),
+ }
+ )
+
+ # 使用权重格式化函数
+ format_weight, _, _ = format_weight_for_display(lotterys_data, "weight")
+
# 根据排序状态对数据进行排序
if self.sort_column >= 0:
# 定义排序键函数
def sort_key(lottery):
if self.sort_column == 0: # 序号
- return lottery.get('id', '')
+ return lottery.get("id", "")
elif self.sort_column == 1: # 名称
- return lottery.get('name', '')
+ return lottery.get("name", "")
elif self.sort_column == 2: # 总次数
- return lottery.get('total_count', 0)
+ return lottery.get("total_count", 0)
elif self.sort_column == 3: # 权重
- return lottery.get('weight', '')
- return ''
-
+ return lottery.get("weight", "")
+ return ""
+
# 应用排序
- reverse_order = (self.sort_order == Qt.SortOrder.DescendingOrder)
+ reverse_order = self.sort_order == Qt.SortOrder.DescendingOrder
lotterys_data.sort(key=sort_key, reverse=reverse_order)
-
+
# 计算本次加载的行范围
start_row = self.current_row
end_row = min(start_row + self.batch_size, self.total_rows)
-
+
# 填充表格数据
for i in range(start_row, end_row):
if i >= len(lotterys_data):
break
-
+
lottery = lotterys_data[i]
row = i
-
+
# 序号
- id_item = create_table_item(lottery.get('id', str(row + 1)))
+ id_item = create_table_item(lottery.get("id", str(row + 1)))
self.table.setItem(row, 0, id_item)
-
+
# 名称
- name_item = create_table_item(lottery.get('name', ''))
+ name_item = create_table_item(lottery.get("name", ""))
self.table.setItem(row, 1, name_item)
-
+
# 总次数
- total_count_item = create_table_item(str(lottery.get('total_count_str', lottery.get('total_count', 0))))
+ total_count_item = create_table_item(
+ str(lottery.get("total_count_str", lottery.get("total_count", 0)))
+ )
self.table.setItem(row, 2, total_count_item)
-
+
# 权重
- weight_item = create_table_item(str(lottery.get('weight', '')).zfill(max_weight_length))
+ weight_item = create_table_item(format_weight(lottery.get("weight", 0)))
self.table.setItem(row, 3, weight_item)
-
+
# 更新当前行数
self.current_row = end_row
-
+
except Exception as e:
logger.error(f"加载奖品数据失败: {e}")
- Dialog(
- "错误",
- f"加载奖品数据失败: {e}",
- self
- ).exec()
-
+ Dialog("错误", f"加载奖品数据失败: {e}", self).exec()
+
def _load_more_sessions_data(self):
"""加载更多会话数据"""
if not self.current_pool_name:
return
try:
- lottery_file = get_resources_path('list/lottery_list', f'{self.current_pool_name}.json')
- history_file = get_resources_path('history/lottery_history', f'{self.current_pool_name}.json')
- with open_file(lottery_file, 'r', encoding='utf-8') as f:
+ lottery_file = get_resources_path(
+ "list/lottery_list", f"{self.current_pool_name}.json"
+ )
+ history_file = get_resources_path(
+ "history/lottery_history", f"{self.current_pool_name}.json"
+ )
+ with open_file(lottery_file, "r", encoding="utf-8") as f:
pool_data = json.load(f)
cleaned_lotterys = []
for name, info in pool_data.items():
- if isinstance(info, dict) and info.get('exist', True):
- cleaned_lotterys.append((
- info.get('id', ''),
- name,
- info.get('weight', '')
- ))
-
+ if isinstance(info, dict) and info.get("exist", True):
+ cleaned_lotterys.append(
+ (info.get("id", ""), name, info.get("weight", ""))
+ )
+
history_data = {}
if file_exists(history_file):
try:
- with open_file(history_file, 'r', encoding='utf-8') as f:
+ with open_file(history_file, "r", encoding="utf-8") as f:
history_data = json.load(f)
except json.JSONDecodeError:
pass
-
- max_id_length = max(len(str(lottery[0])) for lottery in cleaned_lotterys) if cleaned_lotterys else 0
+
+ max_id_length = (
+ max(len(str(lottery[0])) for lottery in cleaned_lotterys)
+ if cleaned_lotterys
+ else 0
+ )
lotterys_data = []
for lottery_id, name, weight in cleaned_lotterys:
- time_records = history_data.get('lotterys', {}).get(name, {}).get('history', [{}])
+ time_records = (
+ history_data.get("lotterys", {}).get(name, {}).get("history", [{}])
+ )
for record in time_records:
- draw_time = record.get('draw_time', '')
- if draw_time:
- lotterys_data.append({
- 'draw_time': draw_time,
- 'id': str(lottery_id).zfill(max_id_length),
- 'name': name,
- 'weight': record.get('weight', '')
- })
+ draw_time = record.get("draw_time", "")
+ if draw_time:
+ lotterys_data.append(
+ {
+ "draw_time": draw_time,
+ "id": str(lottery_id).zfill(max_id_length),
+ "name": name,
+ "weight": record.get("weight", ""),
+ }
+ )
- max_weight_length = max(len(str(lottery['weight'])) for lottery in lotterys_data) if lotterys_data else 0
+ # 使用权重格式化函数
+ format_weight, _, _ = format_weight_for_display(lotterys_data, "weight")
# 根据排序状态对数据进行排序
if self.sort_column >= 0:
# 定义排序键函数
def sort_key(lottery):
if self.sort_column == 0: # 时间
- return lottery.get('draw_time', '')
+ return lottery.get("draw_time", "")
elif self.sort_column == 1: # 序号
- return lottery.get('id', '')
+ return lottery.get("id", "")
elif self.sort_column == 2: # 名称
- return lottery.get('name', '')
+ return lottery.get("name", "")
elif self.sort_column == 3: # 权重
- return lottery.get('weight', '')
- return ''
-
+ return lottery.get("weight", "")
+ return ""
+
# 应用排序
- reverse_order = (self.sort_order == Qt.SortOrder.DescendingOrder)
+ reverse_order = self.sort_order == Qt.SortOrder.DescendingOrder
lotterys_data.sort(key=sort_key, reverse=reverse_order)
else:
# 默认按时间降序排序
- lotterys_data.sort(key=lambda x: x.get('draw_time', ''), reverse=True)
-
+ lotterys_data.sort(key=lambda x: x.get("draw_time", ""), reverse=True)
+
# 计算本次加载的行范围
start_row = self.current_row
end_row = min(start_row + self.batch_size, self.total_rows)
-
+
# 填充表格数据
for i in range(start_row, end_row):
if i >= len(lotterys_data):
break
-
+
lottery = lotterys_data[i]
row = i
-
+
# 时间
- draw_time_item = create_table_item(lottery.get('draw_time', ''))
+ draw_time_item = create_table_item(lottery.get("draw_time", ""))
self.table.setItem(row, 0, draw_time_item)
-
+
# 序号
- id_item = create_table_item(lottery.get('id', str(row + 1)))
+ id_item = create_table_item(lottery.get("id", str(row + 1)))
self.table.setItem(row, 1, id_item)
-
+
# 名称
- name_item = create_table_item(lottery.get('name', ''))
+ name_item = create_table_item(lottery.get("name", ""))
self.table.setItem(row, 2, name_item)
# 权重
- weight_item = create_table_item(str(lottery.get('weight', '')).zfill(max_weight_length))
+ weight_item = create_table_item(format_weight(lottery.get("weight", 0)))
self.table.setItem(row, 3, weight_item)
# 更新当前行数
self.current_row = end_row
-
+
except Exception as e:
logger.error(f"加载会话数据失败: {e}")
- Dialog(
- "错误",
- f"加载会话数据失败: {e}",
- self
- ).exec()
-
+ Dialog("错误", f"加载会话数据失败: {e}", self).exec()
+
def _load_more_stats_data(self, lottery_name):
"""加载更多统计数据"""
if not self.current_pool_name:
return
try:
- lottery_file = get_resources_path('list/lottery_list', f'{self.current_pool_name}.json')
- history_file = get_resources_path('history/lottery_history', f'{self.current_pool_name}.json')
- with open_file(lottery_file, 'r', encoding='utf-8') as f:
+ lottery_file = get_resources_path(
+ "list/lottery_list", f"{self.current_pool_name}.json"
+ )
+ history_file = get_resources_path(
+ "history/lottery_history", f"{self.current_pool_name}.json"
+ )
+ with open_file(lottery_file, "r", encoding="utf-8") as f:
pool_data = json.load(f)
cleaned_lotterys = []
for name, info in pool_data.items():
- if isinstance(info, dict) and info.get('exist', True) and name == lottery_name:
- cleaned_lotterys.append((
- info.get('id', ''),
- name,
- info.get('weight', '')
- ))
-
+ if (
+ isinstance(info, dict)
+ and info.get("exist", True)
+ and name == lottery_name
+ ):
+ cleaned_lotterys.append(
+ (info.get("id", ""), name, info.get("weight", ""))
+ )
+
history_data = {}
if file_exists(history_file):
try:
- with open_file(history_file, 'r', encoding='utf-8') as f:
+ with open_file(history_file, "r", encoding="utf-8") as f:
history_data = json.load(f)
except json.JSONDecodeError:
pass
lotterys_data = []
for lottery_id, name, weight in cleaned_lotterys:
- time_records = history_data.get('lotterys', {}).get(name, {}).get('history', [{}])
+ time_records = (
+ history_data.get("lotterys", {}).get(name, {}).get("history", [{}])
+ )
for record in time_records:
- draw_time = record.get('draw_time', '')
- if draw_time:
- lotterys_data.append({
- 'draw_time': draw_time,
- 'draw_method': str(record.get('draw_method', '')),
- 'draw_lottery_numbers': str(record.get('draw_lottery_numbers', 0)),
- 'weight': record.get('weight', '')
- })
-
- max_weight_length = max(len(str(lottery.get('weight', ''))) for lottery in lotterys_data)
+ draw_time = record.get("draw_time", "")
+ if draw_time:
+ lotterys_data.append(
+ {
+ "draw_time": draw_time,
+ "draw_method": str(record.get("draw_method", "")),
+ "draw_lottery_numbers": str(
+ record.get("draw_lottery_numbers", 0)
+ ),
+ "weight": record.get("weight", ""),
+ }
+ )
+
+ max_weight_length = max(
+ len(str(lottery.get("weight", ""))) for lottery in lotterys_data
+ )
# 根据排序状态对数据进行排序
if self.sort_column >= 0:
# 定义排序键函数
def sort_key(lottery):
if self.sort_column == 0: # 时间
- return lottery.get('draw_time', '')
+ return lottery.get("draw_time", "")
elif self.sort_column == 1: # 模式
- return str(lottery.get('draw_method', ''))
+ return str(lottery.get("draw_method", ""))
elif self.sort_column == 2: # 数量
- return int(lottery.get('draw_lottery_numbers', 0))
+ return int(lottery.get("draw_lottery_numbers", 0))
elif self.sort_column == 3: # 权重
- return float(lottery.get('weight', ''))
- return ''
-
+ return float(lottery.get("weight", ""))
+ return ""
+
# 应用排序
- reverse_order = (self.sort_order == Qt.SortOrder.DescendingOrder)
+ reverse_order = self.sort_order == Qt.SortOrder.DescendingOrder
lotterys_data.sort(key=sort_key, reverse=reverse_order)
else:
# 默认按时间降序排序
- lotterys_data.sort(key=lambda x: x.get('draw_time', ''), reverse=True)
-
+ lotterys_data.sort(key=lambda x: x.get("draw_time", ""), reverse=True)
+
# 计算本次加载的行范围
start_row = self.current_row
end_row = min(start_row + self.batch_size, self.total_rows)
-
+
# 填充表格数据
for i in range(start_row, end_row):
if i >= len(lotterys_data):
break
-
+
lottery = lotterys_data[i]
row = i
-
+
# 时间
- time_item = create_table_item(lottery.get('draw_time', ''))
+ time_item = create_table_item(lottery.get("draw_time", ""))
self.table.setItem(row, 0, time_item)
-
+
# 模式
- mode_item = create_table_item(lottery.get('draw_method', ''))
+ mode_item = create_table_item(lottery.get("draw_method", ""))
self.table.setItem(row, 1, mode_item)
-
+
# 数量
- draw_lottery_numbers_item = create_table_item(str(lottery.get('draw_lottery_numbers', 0)))
+ draw_lottery_numbers_item = create_table_item(
+ str(lottery.get("draw_lottery_numbers", 0))
+ )
self.table.setItem(row, 2, draw_lottery_numbers_item)
# 权重
- weight_item = create_table_item(str(lottery.get('weight', '')).zfill(max_weight_length))
+ weight_item = create_table_item(
+ str(lottery.get("weight", "")).zfill(max_weight_length)
+ )
self.table.setItem(row, 3, weight_item)
# 更新当前行数
self.current_row = end_row
-
+
except Exception as e:
logger.error(f"加载统计数据失败: {e}")
- Dialog(
- "错误",
- f"加载统计数据失败: {e}",
- self
- ).exec()
-
+ Dialog("错误", f"加载统计数据失败: {e}", self).exec()
+
def setup_file_watcher(self):
"""设置文件系统监视器,监控奖池历史记录文件夹的变化"""
lottery_history_dir = get_path("app/resources/history/lottery_history")
@@ -607,29 +677,29 @@ def setup_file_watcher(self):
def on_directory_changed(self, path):
"""当目录内容发生变化时调用此方法
-
+
Args:
path: 发生变化的目录路径
"""
# logger.debug(f"检测到目录变化: {path}")
QTimer.singleShot(1000, self.refresh_pool_history)
-
+
def refresh_pool_history(self):
"""刷新奖池下拉框列表"""
- if not hasattr(self, 'pool_comboBox'):
+ if not hasattr(self, "pool_comboBox"):
return
-
+
# 保存当前选择的奖池名称和索引
current_pool_name = self.pool_comboBox.currentText()
current_index = self.pool_comboBox.currentIndex()
-
+
# 获取最新的奖池历史列表
pool_history = get_all_history_names("lottery")
-
+
# 清空并重新填充下拉框
self.pool_comboBox.clear()
self.pool_comboBox.addItems(pool_history)
-
+
# 如果之前选择的奖池还在列表中,则重新选择它
if current_pool_name and current_pool_name in pool_history:
index = pool_history.index(current_pool_name)
@@ -649,33 +719,35 @@ def refresh_pool_history(self):
else:
# 如果没有奖池历史,设置占位符
self.pool_comboBox.setCurrentIndex(-1)
- self.pool_comboBox.setPlaceholderText(get_content_name_async("lottery_history_table", "select_pool_name"))
+ self.pool_comboBox.setPlaceholderText(
+ get_content_name_async("lottery_history_table", "select_pool_name")
+ )
# 更新current_pool_name
self.current_pool_name = ""
-
- if hasattr(self, 'clear_button'):
+
+ if hasattr(self, "clear_button"):
self.clear_button.setEnabled(bool(self.current_pool_name))
-
+
def on_pool_changed(self, index):
"""奖池选择变化时刷新表格数据"""
- if not hasattr(self, 'pool_comboBox'):
+ if not hasattr(self, "pool_comboBox"):
return
-
+
# 启用或禁用清除按钮
- if hasattr(self, 'clear_button'):
+ if hasattr(self, "clear_button"):
self.clear_button.setEnabled(self.pool_comboBox.currentIndex() >= 0)
-
+
# 更新当前奖池名称
self.current_pool_name = self.pool_comboBox.currentText()
-
+
# 刷新表格数据
self.refresh_data()
-
+
def refresh_data(self):
"""刷新表格数据"""
- if not hasattr(self, 'table'):
+ if not hasattr(self, "table"):
return
- if not hasattr(self, 'pool_comboBox'):
+ if not hasattr(self, "pool_comboBox"):
return
pool_name = self.pool_comboBox.currentText()
if not pool_name:
@@ -683,15 +755,15 @@ def refresh_data(self):
return
self.current_pool_name = pool_name
self.update_table_headers()
-
+
# 重置数据加载状态
self.current_row = 0
self.is_loading = False
self.table.setRowCount(0)
self.table.blockSignals(True)
-
+
try:
- if not hasattr(self, 'mode_comboBox'):
+ if not hasattr(self, "mode_comboBox"):
self.current_mode = 0
if self.current_mode == 0:
# 获取奖品记录数量
@@ -715,9 +787,13 @@ def refresh_data(self):
self._load_more_sessions_data()
else:
# 当模式值大于等于2时,从设置中获取奖品名称
- self.current_lottery_name = readme_settings_async("lottery_history_table", "select_lottery_name")
+ self.current_lottery_name = readme_settings_async(
+ "lottery_history_table", "select_lottery_name"
+ )
# 获取个人统计记录数量
- stats_count = get_individual_statistics("lottery", pool_name, self.current_lottery_name)
+ stats_count = get_individual_statistics(
+ "lottery", pool_name, self.current_lottery_name
+ )
if stats_count:
self.total_rows = stats_count
# 设置初始行数为批次大小或总行数,取较小值
@@ -726,7 +802,7 @@ def refresh_data(self):
# 加载第一批数据
self._load_more_stats_data(self.current_lottery_name)
return
-
+
self.current_mode = self.mode_comboBox.currentIndex()
if self.current_mode == 0:
# 获取奖品记录数量
@@ -750,7 +826,9 @@ def refresh_data(self):
self._load_more_sessions_data()
else:
# 获取个人统计记录数量
- stats_count = get_individual_statistics("lottery", pool_name, self.mode_comboBox.currentText())
+ stats_count = get_individual_statistics(
+ "lottery", pool_name, self.mode_comboBox.currentText()
+ )
if stats_count:
self.total_rows = stats_count
# 设置初始行数为批次大小或总行数,取较小值
@@ -759,41 +837,53 @@ def refresh_data(self):
self.current_lottery_name = self.mode_comboBox.currentText()
# 加载第一批数据
self._load_more_stats_data(self.current_lottery_name)
-
+
# 设置表格列属性
for i in range(self.table.columnCount()):
- self.table.horizontalHeader().setSectionResizeMode(i, QHeaderView.ResizeMode.Stretch)
- self.table.horizontalHeader().setDefaultAlignment(Qt.AlignmentFlag.AlignCenter)
-
+ self.table.horizontalHeader().setSectionResizeMode(
+ i, QHeaderView.ResizeMode.Stretch
+ )
+ self.table.horizontalHeader().setDefaultAlignment(
+ Qt.AlignmentFlag.AlignCenter
+ )
+
# 如果有排序设置,应用排序
if self.sort_column >= 0:
- self.table.horizontalHeader().setSortIndicator(self.sort_column, self.sort_order)
+ self.table.horizontalHeader().setSortIndicator(
+ self.sort_column, self.sort_order
+ )
self.table.horizontalHeader().setSortIndicatorShown(True)
-
+
except Exception as e:
logger.error(f"刷新表格数据失败: {str(e)}")
finally:
self.table.blockSignals(False)
-
+
def update_table_headers(self):
"""更新表格标题"""
- if not hasattr(self, 'table'):
+ if not hasattr(self, "table"):
return
-
- if hasattr(self, 'mode_comboBox'):
+
+ if hasattr(self, "mode_comboBox"):
self.current_mode = self.mode_comboBox.currentIndex()
else:
self.current_mode = 0
-
+
if self.current_mode == 0:
- headers = get_content_name_async("lottery_history_table", "HeaderLabels_all_weight")
+ headers = get_content_name_async(
+ "lottery_history_table", "HeaderLabels_all_weight"
+ )
self.table.setColumnCount(len(headers))
self.table.setHorizontalHeaderLabels(headers)
elif self.current_mode == 1:
- headers = get_content_name_async("lottery_history_table", "HeaderLabels_time_weight")
+ headers = get_content_name_async(
+ "lottery_history_table", "HeaderLabels_time_weight"
+ )
self.table.setColumnCount(len(headers))
self.table.setHorizontalHeaderLabels(headers)
else:
- headers = get_content_name_async("lottery_history_table", "HeaderLabels_Individual_weight")
+ headers = get_content_name_async(
+ "lottery_history_table", "HeaderLabels_Individual_weight"
+ )
self.table.setColumnCount(len(headers))
self.table.setHorizontalHeaderLabels(headers)
diff --git a/app/view/settings/history/roll_call_history_table.py b/app/view/settings/history/roll_call_history_table.py
index 68a8bf73..ab0f6cdb 100644
--- a/app/view/settings/history/roll_call_history_table.py
+++ b/app/view/settings/history/roll_call_history_table.py
@@ -2,18 +2,12 @@
# 导入库
# ==================================================
import json
-import os
-import sys
-import subprocess
-import pandas as pd
-from collections import OrderedDict
-from datetime import datetime
from loguru import logger
-from PyQt6.QtWidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from PySide6.QtNetwork import *
from qfluentwidgets import *
from app.tools.variable import *
@@ -28,88 +22,128 @@
# 点名历史记录表格
# ==================================================
+
class roll_call_history_table(GroupHeaderCardWidget):
"""点名历史记录表格卡片"""
-
- refresh_signal = pyqtSignal()
-
+
+ refresh_signal = Signal()
+
def __init__(self, parent=None):
super().__init__(parent)
self.parent = parent
self.setTitle(get_content_name_async("roll_call_history_table", "title"))
self.setBorderRadius(8)
-
+
# 初始化数据加载器
class_history = get_all_history_names("roll_call")
self.data_loader = None
- self.current_class_name = class_history[0] if class_history else ''
+ self.current_class_name = class_history[0] if class_history else ""
self.current_mode = 0
self.batch_size = 30 # 每次加载的行数
self.current_row = 0 # 当前加载到的行数
- self.total_rows = 0 # 总行数
+ self.total_rows = 0 # 总行数
self.is_loading = False # 是否正在加载数据
-
+
# 创建班级选择区域
QTimer.singleShot(APPLY_DELAY, self.create_class_selection)
-
+
# 创建表格区域
QTimer.singleShot(APPLY_DELAY, self.create_table)
-
+
# 初始化班级列表
QTimer.singleShot(APPLY_DELAY, self.refresh_class_history)
-
+
# 设置文件系统监视器
QTimer.singleShot(APPLY_DELAY, self.setup_file_watcher)
-
+
# 初始化数据
QTimer.singleShot(APPLY_DELAY, self.refresh_data)
-
+
# 连接信号
self.refresh_signal.connect(self.refresh_data)
-
+
def create_class_selection(self):
"""创建班级选择区域"""
self.class_comboBox = ComboBox()
-
+
# 获取班级历史列表并填充下拉框
class_history = get_all_history_names("roll_call")
self.class_comboBox.addItems(class_history)
-
+
# 设置默认选择
if class_history:
- saved_index = readme_settings_async("roll_call_history_table", "select_class_name")
+ saved_index = readme_settings_async(
+ "roll_call_history_table", "select_class_name"
+ )
self.class_comboBox.setCurrentIndex(0)
self.current_class_name = class_history[0]
else:
# 如果没有班级历史,设置占位符
self.class_comboBox.setCurrentIndex(-1)
- self.class_comboBox.setPlaceholderText(get_content_name_async("roll_call_history_table", "select_class_name"))
+ self.class_comboBox.setPlaceholderText(
+ get_content_name_async("roll_call_history_table", "select_class_name")
+ )
self.current_class_name = ""
-
+
self.class_comboBox.currentIndexChanged.connect(self.on_class_changed)
- self.class_comboBox.currentTextChanged.connect(lambda: self.on_class_changed(-1))
+ self.class_comboBox.currentTextChanged.connect(
+ lambda: self.on_class_changed(-1)
+ )
# 选择查看模式
self.all_names = get_all_names("roll_call", self.class_comboBox.currentText())
self.mode_comboBox = ComboBox()
- self.mode_comboBox.addItems(get_content_combo_name_async("roll_call_history_table", "select_mode") + self.all_names)
+ self.mode_comboBox.addItems(
+ get_content_combo_name_async("roll_call_history_table", "select_mode")
+ + self.all_names
+ )
self.mode_comboBox.setCurrentIndex(0)
self.mode_comboBox.currentIndexChanged.connect(self.refresh_data)
# 选择是否查看权重
self.weight_switch = SwitchButton()
- self.weight_switch.setOffText(get_content_switchbutton_name_async("roll_call_history_table", "select_weight", "disable"))
- self.weight_switch.setOnText(get_content_switchbutton_name_async("roll_call_history_table", "select_weight", "enable"))
- self.weight_switch.setChecked(readme_settings_async("roll_call_history_table", "select_weight"))
- self.weight_switch.checkedChanged.connect(lambda: update_settings("roll_call_history_table", "select_weight", self.weight_switch.isChecked()))
+ self.weight_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "roll_call_history_table", "select_weight", "disable"
+ )
+ )
+ self.weight_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "roll_call_history_table", "select_weight", "enable"
+ )
+ )
+ self.weight_switch.setChecked(
+ readme_settings_async("roll_call_history_table", "select_weight")
+ )
+ self.weight_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "roll_call_history_table",
+ "select_weight",
+ self.weight_switch.isChecked(),
+ )
+ )
self.weight_switch.checkedChanged.connect(self.refresh_data)
- self.addGroup(get_theme_icon("ic_fluent_class_20_filled"),
- get_content_name_async("roll_call_history_table", "select_class_name"), get_content_description_async("roll_call_history_table", "select_class_name"), self.class_comboBox)
- self.addGroup(get_theme_icon("ic_fluent_reading_mode_mobile_20_filled"),
- get_content_name_async("roll_call_history_table", "select_mode"), get_content_description_async("roll_call_history_table", "select_mode"), self.mode_comboBox)
- self.addGroup(get_theme_icon("ic_fluent_box_multiple_search_20_filled"),
- get_content_name_async("roll_call_history_table", "select_weight"), get_content_description_async("roll_call_history_table", "select_weight"), self.weight_switch)
+ self.addGroup(
+ get_theme_icon("ic_fluent_class_20_filled"),
+ get_content_name_async("roll_call_history_table", "select_class_name"),
+ get_content_description_async(
+ "roll_call_history_table", "select_class_name"
+ ),
+ self.class_comboBox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_reading_mode_mobile_20_filled"),
+ get_content_name_async("roll_call_history_table", "select_mode"),
+ get_content_description_async("roll_call_history_table", "select_mode"),
+ self.mode_comboBox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_box_multiple_search_20_filled"),
+ get_content_name_async("roll_call_history_table", "select_weight"),
+ get_content_description_async("roll_call_history_table", "select_weight"),
+ self.weight_switch,
+ )
def create_table(self):
"""创建表格区域"""
@@ -124,64 +158,70 @@ def create_table(self):
self.table.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
self.table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
self.table.verticalHeader().hide()
-
+
# 初始化排序状态
self.sort_column = -1
self.sort_order = Qt.SortOrder.AscendingOrder
# 根据当前选择的模式设置表格头
self.update_table_headers()
-
+
# 设置表格属性
for i in range(self.table.columnCount()):
- self.table.horizontalHeader().setSectionResizeMode(i, QHeaderView.ResizeMode.Stretch)
- self.table.horizontalHeader().setDefaultAlignment(Qt.AlignmentFlag.AlignCenter)
+ self.table.horizontalHeader().setSectionResizeMode(
+ i, QHeaderView.ResizeMode.Stretch
+ )
+ self.table.horizontalHeader().setDefaultAlignment(
+ Qt.AlignmentFlag.AlignCenter
+ )
self.table.horizontalHeader().setSectionsClickable(True)
-
+
# 初始状态下不显示排序指示器
self.table.horizontalHeader().setSortIndicatorShown(False)
-
+
# 连接滚动事件,用于分段加载
self.table.verticalScrollBar().valueChanged.connect(self._on_scroll)
-
+
# 连接排序信号,在排序时重新加载数据
self.table.horizontalHeader().sectionClicked.connect(self._on_header_clicked)
-
+
self.layout().addWidget(self.table)
-
+
def _on_scroll(self, value):
"""处理表格滚动事件,实现分段加载
-
+
Args:
value: 滚动条当前位置
"""
# 如果正在加载或没有更多数据,直接返回
if self.is_loading or self.current_row >= self.total_rows:
return
-
+
# 获取滚动条最大值和当前值
max_value = self.table.verticalScrollBar().maximum()
current_value = self.table.verticalScrollBar().value()
-
+
# 使用更精确的滚动检测,确保在滚动到底部时触发
scroll_threshold = max(20, max_value * 0.1) # 至少20像素或10%的位置
if current_value >= max_value - scroll_threshold:
self._load_more_data()
-
+
def _on_header_clicked(self, column):
"""处理表头点击事件,实现排序
-
+
Args:
column: 被点击的列索引
"""
# 如果正在加载数据,不处理排序
if self.is_loading:
return
-
+
# 获取当前排序状态,优先使用我们自己的状态变量
current_sort_column = self.sort_column if self.sort_column >= 0 else -1
- current_sort_order = self.sort_order if self.sort_column >= 0 else Qt.SortOrder.AscendingOrder
-
+ current_sort_order = (
+ self.sort_order if self.sort_column >= 0 else Qt.SortOrder.AscendingOrder
+ )
+
# 如果点击的是同一列,则切换排序顺序;否则设置为升序
if column == current_sort_column:
# 切换排序顺序
@@ -192,22 +232,22 @@ def _on_header_clicked(self, column):
else:
# 点击不同列,设置为升序
new_sort_order = Qt.SortOrder.AscendingOrder
-
+
# 更新排序状态
self.sort_column = column
self.sort_order = new_sort_order
-
+
# 设置排序指示器
self.table.horizontalHeader().setSortIndicator(column, new_sort_order)
self.table.horizontalHeader().setSortIndicatorShown(True)
-
+
# 重置数据加载状态
self.current_row = 0
self.table.setRowCount(0)
-
+
# 重新加载数据
self.refresh_data()
-
+
def _sort_current_data(self):
"""对已加载的数据进行排序,不重新加载数据"""
# 获取当前表格中的所有数据
@@ -221,11 +261,11 @@ def _sort_current_data(self):
else:
row_data.append("")
table_data.append(row_data)
-
+
# 如果没有数据,直接返回
if not table_data:
return
-
+
# 根据排序状态对数据进行排序
def sort_key(row):
# 尝试将数据转换为数字,如果失败则使用字符串比较
@@ -235,7 +275,7 @@ def sort_key(row):
# 权重列可能包含非数字字符,尝试提取数字部分
weight_str = row[self.sort_column]
# 移除可能的前导零
- weight_str = weight_str.lstrip('0')
+ weight_str = weight_str.lstrip("0")
if not weight_str:
return 0.0
return float(weight_str)
@@ -243,435 +283,515 @@ def sort_key(row):
return float(row[self.sort_column])
except (ValueError, IndexError):
return row[self.sort_column]
-
+
# 应用排序
- reverse_order = (self.sort_order == Qt.SortOrder.DescendingOrder)
+ reverse_order = self.sort_order == Qt.SortOrder.DescendingOrder
table_data.sort(key=sort_key, reverse=reverse_order)
-
+
# 清空表格
self.table.setRowCount(0)
-
+
# 重新填充排序后的数据
self.table.setRowCount(len(table_data))
for row_idx, row_data in enumerate(table_data):
for col_idx, cell_data in enumerate(row_data):
item = create_table_item(cell_data)
self.table.setItem(row_idx, col_idx, item)
-
+
def _load_more_data(self):
"""加载更多数据"""
if self.is_loading or self.current_row >= self.total_rows:
return
-
+
self.is_loading = True
-
+
# 计算新的行数
new_row_count = min(self.current_row + self.batch_size, self.total_rows)
-
+
# 增加表格行数
self.table.setRowCount(new_row_count)
-
+
# 根据当前模式加载数据,与refresh_data方法保持一致
- if hasattr(self, 'mode_comboBox'):
+ if hasattr(self, "mode_comboBox"):
self.current_mode = self.mode_comboBox.currentIndex()
else:
self.current_mode = 0
-
+
if self.current_mode == 0:
self._load_more_students_data()
elif self.current_mode == 1:
self._load_more_sessions_data()
else:
# 当模式值大于等于2时,表示选择了特定的学生姓名
- if hasattr(self, 'mode_comboBox'):
+ if hasattr(self, "mode_comboBox"):
self.current_student_name = self.mode_comboBox.currentText()
else:
# 如果没有mode_comboBox,从设置中获取学生姓名
- self.current_student_name = readme_settings_async("roll_call_history_table", "select_student_name")
+ self.current_student_name = readme_settings_async(
+ "roll_call_history_table", "select_student_name"
+ )
self._load_more_stats_data(self.current_student_name)
-
+
# 数据加载完成后启用排序
if self.current_row >= self.total_rows:
self.table.setSortingEnabled(True)
if self.sort_column >= 0:
- self.table.horizontalHeader().setSortIndicator(self.sort_column, self.sort_order)
+ self.table.horizontalHeader().setSortIndicator(
+ self.sort_column, self.sort_order
+ )
self.table.horizontalHeader().setSortIndicatorShown(True)
-
+
self.is_loading = False
-
+
def _load_more_students_data(self):
"""加载更多学生数据"""
if not self.current_class_name:
return
try:
- student_file = get_resources_path('list/roll_call_list', f'{self.current_class_name}.json')
- history_file = get_resources_path('history/roll_call_history', f'{self.current_class_name}.json')
- with open_file(student_file, 'r', encoding='utf-8') as f:
+ student_file = get_resources_path(
+ "list/roll_call_list", f"{self.current_class_name}.json"
+ )
+ history_file = get_resources_path(
+ "history/roll_call_history", f"{self.current_class_name}.json"
+ )
+ with open_file(student_file, "r", encoding="utf-8") as f:
class_data = json.load(f)
cleaned_students = []
for name, info in class_data.items():
- if isinstance(info, dict) and info.get('exist', True):
- cleaned_students.append((
- info.get('id', ''),
- name,
- info.get('gender', ''),
- info.get('group', '')
- ))
-
+ if isinstance(info, dict) and info.get("exist", True):
+ cleaned_students.append(
+ (
+ info.get("id", ""),
+ name,
+ info.get("gender", ""),
+ info.get("group", ""),
+ )
+ )
+
history_data = {}
if file_exists(history_file):
try:
- with open_file(history_file, 'r', encoding='utf-8') as f:
+ with open_file(history_file, "r", encoding="utf-8") as f:
history_data = json.load(f)
except json.JSONDecodeError:
pass
-
- max_id_length = max(len(str(student[0])) for student in cleaned_students) if cleaned_students else 0
- max_total_count_length = max(len(str(history_data.get('students', {}).get(name, {}).get('total_count', 0))) for _, name, _, _ in cleaned_students) if cleaned_students else 0
+
+ max_id_length = (
+ max(len(str(student[0])) for student in cleaned_students)
+ if cleaned_students
+ else 0
+ )
+ max_total_count_length = (
+ max(
+ len(
+ str(
+ history_data.get("students", {})
+ .get(name, {})
+ .get("total_count", 0)
+ )
+ )
+ for _, name, _, _ in cleaned_students
+ )
+ if cleaned_students
+ else 0
+ )
students_data = []
for student_id, name, gender, group in cleaned_students:
- count = int(history_data.get('students', {}).get(name, {}).get('total_count', 0))
- group_gender_count = int(history_data.get('students', {}).get(name, {}).get('group_gender_count', 0))
+ count = int(
+ history_data.get("students", {}).get(name, {}).get("total_count", 0)
+ )
+ group_gender_count = int(
+ history_data.get("students", {})
+ .get(name, {})
+ .get("group_gender_count", 0)
+ )
total_count = count + group_gender_count
- students_data.append({
- 'id': str(student_id).zfill(max_id_length),
- 'name': name,
- 'gender': gender,
- 'group': group,
- 'total_count': total_count,
- 'total_count_str': str(total_count).zfill(max_total_count_length)
- })
-
- students_weight_data = calculate_weight(students_data)
- max_weight_length = max(len(str(student.get('next_weight', ''))) for student in students_weight_data) if students_weight_data else 0
-
+ students_data.append(
+ {
+ "id": str(student_id).zfill(max_id_length),
+ "name": name,
+ "gender": gender,
+ "group": group,
+ "total_count": total_count,
+ "total_count_str": str(total_count).zfill(
+ max_total_count_length
+ ),
+ }
+ )
+
+ students_weight_data = calculate_weight(
+ students_data, self.current_class_name
+ )
+
+ # 使用权重格式化函数
+ format_weight, _, _ = format_weight_for_display(
+ students_weight_data, "next_weight"
+ )
+
# 根据排序状态对数据进行排序
if self.sort_column >= 0:
# 定义排序键函数
def sort_key(student):
if self.sort_column == 0: # 学号
- return student.get('id', '')
+ return student.get("id", "")
elif self.sort_column == 1: # 姓名
- return student.get('name', '')
+ return student.get("name", "")
elif self.sort_column == 2: # 性别
- return student.get('gender', '')
+ return student.get("gender", "")
elif self.sort_column == 3: # 小组
- return student.get('group', '')
+ return student.get("group", "")
elif self.sort_column == 4: # 总次数
- return student.get('total_count', 0)
+ return student.get("total_count", 0)
elif self.sort_column == 5: # 权重
# 使用学生ID和姓名在权重数据中查找对应的权重
for weight_student in students_weight_data:
- if weight_student.get('id') == student.get('id') and weight_student.get('name') == student.get('name'):
- return weight_student.get('next_weight', 1.0)
+ if weight_student.get("id") == student.get(
+ "id"
+ ) and weight_student.get("name") == student.get("name"):
+ return weight_student.get("next_weight", 1.0)
return 1.0
- return ''
-
+ return ""
+
# 应用排序
- reverse_order = (self.sort_order == Qt.SortOrder.DescendingOrder)
+ reverse_order = self.sort_order == Qt.SortOrder.DescendingOrder
students_data.sort(key=sort_key, reverse=reverse_order)
# 同步排序权重数据
sorted_weight_data = []
for student in students_data:
for weight_student in students_weight_data:
- if weight_student.get('id') == student.get('id') and weight_student.get('name') == student.get('name'):
+ if weight_student.get("id") == student.get(
+ "id"
+ ) and weight_student.get("name") == student.get("name"):
sorted_weight_data.append(weight_student)
break
students_weight_data = sorted_weight_data
-
+
# 计算本次加载的行范围
start_row = self.current_row
end_row = min(start_row + self.batch_size, self.total_rows)
-
+
# 填充表格数据
for i in range(start_row, end_row):
if i >= len(students_data):
break
-
+
student = students_data[i]
row = i
-
+
# 学号
- id_item = create_table_item(student.get('id', str(row + 1)))
+ id_item = create_table_item(student.get("id", str(row + 1)))
self.table.setItem(row, 0, id_item)
-
+
# 姓名
- name_item = create_table_item(student.get('name', ''))
+ name_item = create_table_item(student.get("name", ""))
self.table.setItem(row, 1, name_item)
-
+
# 性别
- gender_item = create_table_item(student.get('gender', ''))
+ gender_item = create_table_item(student.get("gender", ""))
self.table.setItem(row, 2, gender_item)
-
+
# 小组
- group_item = create_table_item(student.get('group', ''))
+ group_item = create_table_item(student.get("group", ""))
self.table.setItem(row, 3, group_item)
-
+
# 总次数
- total_count_item = create_table_item(str(student.get('total_count_str', student.get('total_count', 0))))
+ total_count_item = create_table_item(
+ str(student.get("total_count_str", student.get("total_count", 0)))
+ )
self.table.setItem(row, 4, total_count_item)
-
+
# 如果需要显示权重
if self.table.columnCount() > 5:
- weight_item = create_table_item(str(students_weight_data[i].get('next_weight', '')).zfill(max_weight_length))
+ weight_item = create_table_item(
+ str(
+ format_weight(
+ students_weight_data[i].get("next_weight", "")
+ )
+ )
+ )
self.table.setItem(row, 5, weight_item)
-
+
# 更新当前行数
self.current_row = end_row
-
+
except Exception as e:
logger.error(f"加载学生数据失败: {e}")
- Dialog(
- "错误",
- f"加载学生数据失败: {e}",
- self
- ).exec()
-
+ Dialog("错误", f"加载学生数据失败: {e}", self).exec()
+
def _load_more_sessions_data(self):
"""加载更多会话数据"""
if not self.current_class_name:
return
try:
- student_file = get_resources_path('list/roll_call_list', f'{self.current_class_name}.json')
- history_file = get_resources_path('history/roll_call_history', f'{self.current_class_name}.json')
- with open_file(student_file, 'r', encoding='utf-8') as f:
+ student_file = get_resources_path(
+ "list/roll_call_list", f"{self.current_class_name}.json"
+ )
+ history_file = get_resources_path(
+ "history/roll_call_history", f"{self.current_class_name}.json"
+ )
+ with open_file(student_file, "r", encoding="utf-8") as f:
class_data = json.load(f)
cleaned_students = []
for name, info in class_data.items():
- if isinstance(info, dict) and info.get('exist', True):
- cleaned_students.append((
- info.get('id', ''),
- name,
- info.get('gender', ''),
- info.get('group', '')
- ))
-
+ if isinstance(info, dict) and info.get("exist", True):
+ cleaned_students.append(
+ (
+ info.get("id", ""),
+ name,
+ info.get("gender", ""),
+ info.get("group", ""),
+ )
+ )
+
history_data = {}
if file_exists(history_file):
try:
- with open_file(history_file, 'r', encoding='utf-8') as f:
+ with open_file(history_file, "r", encoding="utf-8") as f:
history_data = json.load(f)
except json.JSONDecodeError:
pass
-
- max_id_length = max(len(str(student[0])) for student in cleaned_students) if cleaned_students else 0
+
+ max_id_length = (
+ max(len(str(student[0])) for student in cleaned_students)
+ if cleaned_students
+ else 0
+ )
students_data = []
for student_id, name, gender, group in cleaned_students:
- time_records = history_data.get('students', {}).get(name, {}).get('history', [{}])
+ time_records = (
+ history_data.get("students", {}).get(name, {}).get("history", [{}])
+ )
for record in time_records:
- draw_time = record.get('draw_time', '')
- if draw_time:
- students_data.append({
- 'draw_time': draw_time,
- 'id': str(student_id).zfill(max_id_length),
- 'name': name,
- 'gender': gender,
- 'group': group,
- 'weight': record.get('weight', '')
- })
-
- max_weight_length = max(len(str(student.get('weight', ''))) for student in students_data)
+ draw_time = record.get("draw_time", "")
+ if draw_time:
+ students_data.append(
+ {
+ "draw_time": draw_time,
+ "id": str(student_id).zfill(max_id_length),
+ "name": name,
+ "gender": gender,
+ "group": group,
+ "weight": record.get("weight", ""),
+ }
+ )
+
+ # 使用权重格式化函数
+ format_weight, _, _ = format_weight_for_display(students_data, "weight")
# 根据排序状态对数据进行排序
if self.sort_column >= 0:
# 定义排序键函数
def sort_key(student):
if self.sort_column == 0: # 时间
- return student.get('draw_time', '')
+ return student.get("draw_time", "")
elif self.sort_column == 1: # 学号
- return student.get('id', '')
+ return student.get("id", "")
elif self.sort_column == 2: # 姓名
- return student.get('name', '')
+ return student.get("name", "")
elif self.sort_column == 3: # 性别
- return student.get('gender', '')
+ return student.get("gender", "")
elif self.sort_column == 4: # 小组
- return student.get('group', '')
+ return student.get("group", "")
elif self.sort_column == 5: # 权重
- return student.get('weight', '')
- return ''
-
+ return student.get("weight", "")
+ return ""
+
# 应用排序
- reverse_order = (self.sort_order == Qt.SortOrder.DescendingOrder)
+ reverse_order = self.sort_order == Qt.SortOrder.DescendingOrder
students_data.sort(key=sort_key, reverse=reverse_order)
else:
# 默认按时间降序排序
- students_data.sort(key=lambda x: x.get('draw_time', ''), reverse=True)
-
+ students_data.sort(key=lambda x: x.get("draw_time", ""), reverse=True)
+
# 计算本次加载的行范围
start_row = self.current_row
end_row = min(start_row + self.batch_size, self.total_rows)
-
+
# 填充表格数据
for i in range(start_row, end_row):
if i >= len(students_data):
break
-
+
student = students_data[i]
row = i
-
+
# 时间
- draw_time_item = create_table_item(student.get('draw_time', ''))
+ draw_time_item = create_table_item(student.get("draw_time", ""))
self.table.setItem(row, 0, draw_time_item)
-
+
# 学号
- id_item = create_table_item(student.get('id', str(row + 1)))
+ id_item = create_table_item(student.get("id", str(row + 1)))
self.table.setItem(row, 1, id_item)
-
+
# 姓名
- name_item = create_table_item(student.get('name', ''))
+ name_item = create_table_item(student.get("name", ""))
self.table.setItem(row, 2, name_item)
-
+
# 性别
- gender = student.get('gender', '')
- gender_item = create_table_item(str(gender) if gender else '')
+ gender = student.get("gender", "")
+ gender_item = create_table_item(str(gender) if gender else "")
self.table.setItem(row, 3, gender_item)
-
+
# 小组
- group = student.get('group', '')
- group_item = create_table_item(str(group) if group else '')
+ group = student.get("group", "")
+ group_item = create_table_item(str(group) if group else "")
self.table.setItem(row, 4, group_item)
# 如果需要显示权重
if self.table.columnCount() > 5:
- weight_item = create_table_item(str(student.get('weight', '')).zfill(max_weight_length))
+ weight_item = create_table_item(
+ str(format_weight(student.get("weight", "")))
+ )
self.table.setItem(row, 5, weight_item)
# 更新当前行数
self.current_row = end_row
-
+
except Exception as e:
logger.error(f"加载会话数据失败: {e}")
- Dialog(
- "错误",
- f"加载会话数据失败: {e}",
- self
- ).exec()
-
+ Dialog("错误", f"加载会话数据失败: {e}", self).exec()
+
def _load_more_stats_data(self, student_name):
"""加载更多统计数据"""
if not self.current_class_name:
return
try:
- student_file = get_resources_path('list/roll_call_list', f'{self.current_class_name}.json')
- history_file = get_resources_path('history/roll_call_history', f'{self.current_class_name}.json')
- with open_file(student_file, 'r', encoding='utf-8') as f:
+ student_file = get_resources_path(
+ "list/roll_call_list", f"{self.current_class_name}.json"
+ )
+ history_file = get_resources_path(
+ "history/roll_call_history", f"{self.current_class_name}.json"
+ )
+ with open_file(student_file, "r", encoding="utf-8") as f:
class_data = json.load(f)
cleaned_students = []
for name, info in class_data.items():
- if isinstance(info, dict) and info.get('exist', True) and name == student_name:
- cleaned_students.append((
- info.get('id', ''),
- name,
- info.get('gender', ''),
- info.get('group', '')
- ))
-
+ if (
+ isinstance(info, dict)
+ and info.get("exist", True)
+ and name == student_name
+ ):
+ cleaned_students.append(
+ (
+ info.get("id", ""),
+ name,
+ info.get("gender", ""),
+ info.get("group", ""),
+ )
+ )
+
history_data = {}
if file_exists(history_file):
try:
- with open_file(history_file, 'r', encoding='utf-8') as f:
+ with open_file(history_file, "r", encoding="utf-8") as f:
history_data = json.load(f)
except json.JSONDecodeError:
pass
students_data = []
for student_id, name, gender, group in cleaned_students:
- time_records = history_data.get('students', {}).get(name, {}).get('history', [{}])
+ time_records = (
+ history_data.get("students", {}).get(name, {}).get("history", [{}])
+ )
for record in time_records:
- draw_time = record.get('draw_time', '')
- if draw_time:
- students_data.append({
- 'draw_time': draw_time,
- 'draw_method': str(record.get('draw_method', '')),
- 'draw_people_numbers': str(record.get('draw_people_numbers', 0)),
- 'draw_gender': str(record.get('draw_gender', '')),
- 'draw_group': str(record.get('draw_group', '')),
- 'weight': record.get('weight', '')
- })
-
- max_weight_length = max(len(str(student.get('weight', ''))) for student in students_data)
+ draw_time = record.get("draw_time", "")
+ if draw_time:
+ students_data.append(
+ {
+ "draw_time": draw_time,
+ "draw_method": str(record.get("draw_method", "")),
+ "draw_people_numbers": str(
+ record.get("draw_people_numbers", 0)
+ ),
+ "draw_gender": str(record.get("draw_gender", "")),
+ "draw_group": str(record.get("draw_group", "")),
+ "weight": record.get("weight", ""),
+ }
+ )
+
+ # 使用权重格式化函数
+ format_weight, _, _ = format_weight_for_display(students_data, "weight")
# 根据排序状态对数据进行排序
if self.sort_column >= 0:
# 定义排序键函数
def sort_key(student):
if self.sort_column == 0: # 时间
- return student.get('draw_time', '')
+ return student.get("draw_time", "")
elif self.sort_column == 1: # 模式
- return str(student.get('draw_method', ''))
+ return str(student.get("draw_method", ""))
elif self.sort_column == 2: # 人数
- return int(student.get('draw_people_numbers', 0))
+ return int(student.get("draw_people_numbers", 0))
elif self.sort_column == 3: # 性别
- return str(student.get('draw_gender', ''))
+ return str(student.get("draw_gender", ""))
elif self.sort_column == 4: # 小组
- return str(student.get('draw_group', ''))
+ return str(student.get("draw_group", ""))
elif self.sort_column == 5: # 权重
- return float(student.get('weight', ''))
- return ''
-
+ return float(student.get("weight", ""))
+ return ""
+
# 应用排序
- reverse_order = (self.sort_order == Qt.SortOrder.DescendingOrder)
+ reverse_order = self.sort_order == Qt.SortOrder.DescendingOrder
students_data.sort(key=sort_key, reverse=reverse_order)
else:
# 默认按时间降序排序
- students_data.sort(key=lambda x: x.get('draw_time', ''), reverse=True)
-
+ students_data.sort(key=lambda x: x.get("draw_time", ""), reverse=True)
+
# 计算本次加载的行范围
start_row = self.current_row
end_row = min(start_row + self.batch_size, self.total_rows)
-
+
# 填充表格数据
for i in range(start_row, end_row):
if i >= len(students_data):
break
-
+
student = students_data[i]
row = i
-
+
# 时间
- time_item = create_table_item(student.get('draw_time', ''))
+ time_item = create_table_item(student.get("draw_time", ""))
self.table.setItem(row, 0, time_item)
-
+
# 模式
- mode_item = create_table_item(student.get('draw_method', ''))
+ mode_item = create_table_item(student.get("draw_method", ""))
self.table.setItem(row, 1, mode_item)
-
+
# 人数
- draw_people_numbers_item = create_table_item(str(student.get('draw_people_numbers', 0)))
+ draw_people_numbers_item = create_table_item(
+ str(student.get("draw_people_numbers", 0))
+ )
self.table.setItem(row, 2, draw_people_numbers_item)
-
+
# 性别
- draw_gender = student.get('draw_gender', '')
- gender_item = create_table_item(draw_gender if draw_gender else '')
+ draw_gender = student.get("draw_gender", "")
+ gender_item = create_table_item(draw_gender if draw_gender else "")
self.table.setItem(row, 3, gender_item)
-
+
# 小组
- draw_group = student.get('draw_group', '')
- group_item = create_table_item(draw_group if draw_group else '')
+ draw_group = student.get("draw_group", "")
+ group_item = create_table_item(draw_group if draw_group else "")
self.table.setItem(row, 4, group_item)
# 如果需要显示权重
if self.table.columnCount() > 5:
- weight_item = create_table_item(str(student.get('weight', '')).zfill(max_weight_length))
+ weight_item = create_table_item(
+ str(format_weight(student.get("weight", 0)))
+ )
self.table.setItem(row, 5, weight_item)
-
# 更新当前行数
self.current_row = end_row
-
+
except Exception as e:
logger.error(f"加载统计数据失败: {e}")
- Dialog(
- "错误",
- f"加载统计数据失败: {e}",
- self
- ).exec()
-
+ Dialog("错误", f"加载统计数据失败: {e}", self).exec()
+
def setup_file_watcher(self):
"""设置文件系统监视器,监控班级历史记录文件夹的变化"""
roll_call_history_dir = get_path("app/resources/history/roll_call_history")
@@ -685,36 +805,38 @@ def setup_file_watcher(self):
def on_directory_changed(self, path):
"""当目录内容发生变化时调用此方法
-
+
Args:
path: 发生变化的目录路径
"""
# logger.debug(f"检测到目录变化: {path}")
QTimer.singleShot(1000, self.refresh_class_history)
-
+
def refresh_class_history(self):
"""刷新班级下拉框列表"""
- if not hasattr(self, 'class_comboBox'):
+ if not hasattr(self, "class_comboBox"):
return
-
+
# 保存当前选择的班级名称和索引
current_class_name = self.class_comboBox.currentText()
current_index = self.class_comboBox.currentIndex()
-
+
# 获取最新的班级历史列表
class_history = get_all_history_names("roll_call")
-
+
# 清空并重新填充下拉框
self.class_comboBox.clear()
self.class_comboBox.addItems(class_history)
-
+
# 如果之前选择的班级还在列表中,则重新选择它
if current_class_name and current_class_name in class_history:
index = class_history.index(current_class_name)
self.class_comboBox.setCurrentIndex(index)
# 更新current_class_name
self.current_class_name = current_class_name
- elif class_history and current_index >= 0 and current_index < len(class_history):
+ elif (
+ class_history and current_index >= 0 and current_index < len(class_history)
+ ):
# 如果之前选择的索引仍然有效,使用相同的索引
self.class_comboBox.setCurrentIndex(current_index)
# 更新current_class_name
@@ -727,33 +849,35 @@ def refresh_class_history(self):
else:
# 如果没有班级历史,设置占位符
self.class_comboBox.setCurrentIndex(-1)
- self.class_comboBox.setPlaceholderText(get_content_name_async("roll_call_history_table", "select_class_name"))
+ self.class_comboBox.setPlaceholderText(
+ get_content_name_async("roll_call_history_table", "select_class_name")
+ )
# 更新current_class_name
self.current_class_name = ""
-
- if hasattr(self, 'clear_button'):
+
+ if hasattr(self, "clear_button"):
self.clear_button.setEnabled(bool(self.current_class_name))
-
+
def on_class_changed(self, index):
"""班级选择变化时刷新表格数据"""
- if not hasattr(self, 'class_comboBox'):
+ if not hasattr(self, "class_comboBox"):
return
-
+
# 启用或禁用清除按钮
- if hasattr(self, 'clear_button'):
+ if hasattr(self, "clear_button"):
self.clear_button.setEnabled(self.class_comboBox.currentIndex() >= 0)
-
+
# 更新当前班级名称
self.current_class_name = self.class_comboBox.currentText()
-
+
# 刷新表格数据
self.refresh_data()
-
+
def refresh_data(self):
"""刷新表格数据"""
- if not hasattr(self, 'table'):
+ if not hasattr(self, "table"):
return
- if not hasattr(self, 'class_comboBox'):
+ if not hasattr(self, "class_comboBox"):
return
class_name = self.class_comboBox.currentText()
if not class_name:
@@ -761,15 +885,15 @@ def refresh_data(self):
return
self.current_class_name = class_name
self.update_table_headers()
-
+
# 重置数据加载状态
self.current_row = 0
self.is_loading = False
self.table.setRowCount(0)
self.table.blockSignals(True)
-
+
try:
- if not hasattr(self, 'mode_comboBox'):
+ if not hasattr(self, "mode_comboBox"):
self.current_mode = 0
if self.current_mode == 0:
# 获取学生记录数量
@@ -793,9 +917,13 @@ def refresh_data(self):
self._load_more_sessions_data()
else:
# 当模式值大于等于2时,从设置中获取学生姓名
- self.current_student_name = readme_settings_async("roll_call_history_table", "select_student_name")
+ self.current_student_name = readme_settings_async(
+ "roll_call_history_table", "select_student_name"
+ )
# 获取个人统计记录数量
- stats_count = get_individual_statistics("roll_call", class_name, self.current_student_name)
+ stats_count = get_individual_statistics(
+ "roll_call", class_name, self.current_student_name
+ )
if stats_count:
self.total_rows = stats_count
# 设置初始行数为批次大小或总行数,取较小值
@@ -804,7 +932,7 @@ def refresh_data(self):
# 加载第一批数据
self._load_more_stats_data(self.current_student_name)
return
-
+
self.current_mode = self.mode_comboBox.currentIndex()
if self.current_mode == 0:
# 获取学生记录数量
@@ -827,7 +955,9 @@ def refresh_data(self):
# 加载第一批数据
self._load_more_sessions_data()
else:
- stats = get_individual_statistics("roll_call", class_name, self.mode_comboBox.currentText())
+ stats = get_individual_statistics(
+ "roll_call", class_name, self.mode_comboBox.currentText()
+ )
if stats:
self.total_rows = stats
# 设置初始行数为批次大小或总行数,取较小值
@@ -836,50 +966,68 @@ def refresh_data(self):
self.current_student_name = self.mode_comboBox.currentText()
# 加载第一批数据
self._load_more_stats_data(self.current_student_name)
-
+
# 设置表格列属性
for i in range(self.table.columnCount()):
- self.table.horizontalHeader().setSectionResizeMode(i, QHeaderView.ResizeMode.Stretch)
- self.table.horizontalHeader().setDefaultAlignment(Qt.AlignmentFlag.AlignCenter)
-
+ self.table.horizontalHeader().setSectionResizeMode(
+ i, QHeaderView.ResizeMode.Stretch
+ )
+ self.table.horizontalHeader().setDefaultAlignment(
+ Qt.AlignmentFlag.AlignCenter
+ )
+
# 如果有排序设置,应用排序
if self.sort_column >= 0:
- self.table.horizontalHeader().setSortIndicator(self.sort_column, self.sort_order)
+ self.table.horizontalHeader().setSortIndicator(
+ self.sort_column, self.sort_order
+ )
self.table.horizontalHeader().setSortIndicatorShown(True)
-
+
except Exception as e:
logger.error(f"刷新表格数据失败: {str(e)}")
finally:
self.table.blockSignals(False)
-
+
def update_table_headers(self):
"""更新表格标题"""
- if not hasattr(self, 'table'):
+ if not hasattr(self, "table"):
return
-
- if hasattr(self, 'mode_comboBox'):
+
+ if hasattr(self, "mode_comboBox"):
self.current_mode = self.mode_comboBox.currentIndex()
else:
self.current_mode = 0
-
+
if self.current_mode == 0:
if readme_settings_async("roll_call_history_table", "select_weight"):
- headers = get_content_name_async("roll_call_history_table", "HeaderLabels_all_weight")
+ headers = get_content_name_async(
+ "roll_call_history_table", "HeaderLabels_all_weight"
+ )
else:
- headers = get_content_name_async("roll_call_history_table", "HeaderLabels_all_not_weight")
+ headers = get_content_name_async(
+ "roll_call_history_table", "HeaderLabels_all_not_weight"
+ )
self.table.setColumnCount(len(headers))
self.table.setHorizontalHeaderLabels(headers)
elif self.current_mode == 1:
if readme_settings_async("roll_call_history_table", "select_weight"):
- headers = get_content_name_async("roll_call_history_table", "HeaderLabels_time_weight")
+ headers = get_content_name_async(
+ "roll_call_history_table", "HeaderLabels_time_weight"
+ )
else:
- headers = get_content_name_async("roll_call_history_table", "HeaderLabels_time_not_weight")
+ headers = get_content_name_async(
+ "roll_call_history_table", "HeaderLabels_time_not_weight"
+ )
self.table.setColumnCount(len(headers))
self.table.setHorizontalHeaderLabels(headers)
else:
if readme_settings_async("roll_call_history_table", "select_weight"):
- headers = get_content_name_async("roll_call_history_table", "HeaderLabels_Individual_weight")
+ headers = get_content_name_async(
+ "roll_call_history_table", "HeaderLabels_Individual_weight"
+ )
else:
- headers = get_content_name_async("roll_call_history_table", "HeaderLabels_Individual_not_weight")
+ headers = get_content_name_async(
+ "roll_call_history_table", "HeaderLabels_Individual_not_weight"
+ )
self.table.setColumnCount(len(headers))
self.table.setHorizontalHeaderLabels(headers)
diff --git a/app/view/settings/home.py b/app/view/settings/home.py
index 5761ed44..95b773e4 100644
--- a/app/view/settings/home.py
+++ b/app/view/settings/home.py
@@ -1,24 +1,11 @@
# ==================================================
# 导入库
# ==================================================
-import json
-import os
-import sys
-import subprocess
from loguru import logger
-from PyQt6.QtWidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
-from qfluentwidgets import *
-
-from app.tools.variable import *
-from app.tools.path_utils import *
-from app.tools.personalised import *
-from app.tools.settings_access import *
-from app.tools.settings_default import *
-from app.Language.obtain_language import *
+from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout
+from qfluentwidgets import SearchLineEdit
+
# ==================================================
# 主页
@@ -26,37 +13,37 @@
class home(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
-
+
# 创建主布局
self.main_layout = QVBoxLayout(self)
self.main_layout.setContentsMargins(20, 20, 20, 20)
self.main_layout.setSpacing(10)
-
+
# 创建顶部布局
self.top_layout = QHBoxLayout()
self.top_layout.setContentsMargins(0, 0, 0, 0)
-
+
# 创建搜索框
self.search_line_edit = SearchLineEdit()
self.search_line_edit.setPlaceholderText("搜索...")
self.search_line_edit.setFixedWidth(300)
self.search_line_edit.searchSignal.connect(self.on_search)
-
+
# 将搜索框添加到顶部布局的中间
self.top_layout.addStretch()
self.top_layout.addWidget(self.search_line_edit)
self.top_layout.addStretch()
-
+
# 将顶部布局添加到主布局
self.main_layout.addLayout(self.top_layout)
-
+
# 添加一个弹性空间,使搜索框位于顶部
self.main_layout.addStretch()
-
+
# 设置窗口布局
self.setLayout(self.main_layout)
-
+
def on_search(self, text):
"""搜索框的回调函数"""
- logger.info(f"搜索内容: {text}")
- # 这里可以添加搜索功能的实现
\ No newline at end of file
+ logger.debug(f"搜索内容: {text}")
+ # 这里可以添加搜索功能的实现
diff --git a/app/view/settings/list_management/__init__.py b/app/view/settings/list_management/__init__.py
new file mode 100644
index 00000000..b967d133
--- /dev/null
+++ b/app/view/settings/list_management/__init__.py
@@ -0,0 +1 @@
+"""List management settings pages."""
diff --git a/app/view/settings/list_management/custom_draw_list.py b/app/view/settings/list_management/custom_draw_list.py
new file mode 100644
index 00000000..a1dc2f8f
--- /dev/null
+++ b/app/view/settings/list_management/custom_draw_list.py
@@ -0,0 +1,36 @@
+# ==================================================
+# 导入库
+# ==================================================
+
+from PyQt6.QtWidgets import *
+from PyQt6.QtGui import *
+from PyQt6.QtCore import *
+from PyQt6.QtNetwork import *
+from qfluentwidgets import *
+
+from app.tools.variable import *
+from app.tools.path_utils import *
+from app.tools.personalised import *
+from app.tools.settings_default import *
+from app.tools.settings_access import *
+from app.Language.obtain_language import *
+
+# ==================================================
+# 自定义抽奖名单
+# ==================================================
+class custom_draw_list(GroupHeaderCardWidget):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.setTitle(get_content_name_async("custom_draw_list", "title"))
+ self.setBorderRadius(8)
+
+ # 开机自启设置
+ self.autostart_switch = SwitchButton()
+ self.autostart_switch.setOffText(get_content_switchbutton_name_async("basic_settings", "autostart", "disable"))
+ self.autostart_switch.setOnText(get_content_switchbutton_name_async("basic_settings", "autostart", "enable"))
+ self.autostart_switch.setChecked(readme_settings_async("basic_settings", "autostart"))
+ self.autostart_switch.checkedChanged.connect(lambda: update_settings("basic_settings", "autostart", self.autostart_switch.isChecked()))
+
+ # 添加设置项到分组
+ self.addGroup(get_theme_icon("ic_fluent_arrow_sync_20_filled"),
+ get_content_name_async("basic_settings", "autostart"), get_content_description_async("basic_settings", "autostart"), self.autostart_switch)
diff --git a/app/view/settings/list_management/lottery_list.py b/app/view/settings/list_management/lottery_list.py
index 63691e66..e77573c2 100644
--- a/app/view/settings/list_management/lottery_list.py
+++ b/app/view/settings/list_management/lottery_list.py
@@ -1,16 +1,12 @@
# ==================================================
# 导入库
# ==================================================
-import json
-import os
-import sys
-import subprocess
from loguru import logger
-from PyQt6.QtWidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from PySide6.QtNetwork import *
from qfluentwidgets import *
from app.tools.variable import *
@@ -21,6 +17,7 @@
from app.Language.obtain_language import *
from app.tools.list import *
+
# ==================================================
# 抽奖名单
# ==================================================
@@ -31,48 +28,92 @@ def __init__(self, parent=None):
self.setBorderRadius(8)
# 设置班级名称按钮
- self.pool_name_button = PushButton(get_content_name_async("lottery_list", "set_pool_name"))
+ self.pool_name_button = PushButton(
+ get_content_name_async("lottery_list", "set_pool_name")
+ )
self.pool_name_button.clicked.connect(lambda: self.set_pool_name())
# 选择奖池下拉框
self.pool_name_combo = ComboBox()
self.refresh_pool_list() # 初始化奖池列表
- self.pool_name_combo.setCurrentIndex(readme_settings_async("lottery_list", "select_pool_name"))
+ self.pool_name_combo.setCurrentIndex(
+ readme_settings_async("lottery_list", "select_pool_name")
+ )
if not get_pool_name_list():
self.pool_name_combo.setCurrentIndex(-1)
- self.pool_name_combo.setPlaceholderText(get_content_name_async("lottery_list", "select_pool_name"))
- self.pool_name_combo.currentIndexChanged.connect(lambda: update_settings("lottery_list", "select_pool_name", self.pool_name_combo.currentIndex()))
+ self.pool_name_combo.setPlaceholderText(
+ get_content_name_async("lottery_list", "select_pool_name")
+ )
+ self.pool_name_combo.currentIndexChanged.connect(
+ lambda: update_settings(
+ "lottery_list", "select_pool_name", self.pool_name_combo.currentIndex()
+ )
+ )
# 导入奖品名单按钮
- self.import_prize_button = PushButton(get_content_name_async("lottery_list", "import_prize_name"))
+ self.import_prize_button = PushButton(
+ get_content_name_async("lottery_list", "import_prize_name")
+ )
self.import_prize_button.clicked.connect(lambda: self.import_prize_name())
# 奖品设置按钮
- self.prize_setting_button = PushButton(get_content_name_async("lottery_list", "prize_setting"))
+ self.prize_setting_button = PushButton(
+ get_content_name_async("lottery_list", "prize_setting")
+ )
self.prize_setting_button.clicked.connect(lambda: self.prize_setting())
# 奖品权重设置按钮
- self.prize_weight_setting_button = PushButton(get_content_name_async("lottery_list", "prize_weight_setting"))
- self.prize_weight_setting_button.clicked.connect(lambda: self.prize_weight_setting())
+ self.prize_weight_setting_button = PushButton(
+ get_content_name_async("lottery_list", "prize_weight_setting")
+ )
+ self.prize_weight_setting_button.clicked.connect(
+ lambda: self.prize_weight_setting()
+ )
# 导出奖品名单按钮
- self.export_prize_button = PushButton(get_content_name_async("lottery_list", "export_prize_name"))
+ self.export_prize_button = PushButton(
+ get_content_name_async("lottery_list", "export_prize_name")
+ )
self.export_prize_button.clicked.connect(lambda: self.export_prize_name())
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_slide_text_edit_20_filled"),
- get_content_name_async("lottery_list", "set_pool_name"), get_content_description_async("lottery_list", "set_pool_name"), self.pool_name_button)
- self.addGroup(get_theme_icon("ic_fluent_class_20_filled"),
- get_content_name_async("lottery_list", "select_pool_name"), get_content_description_async("lottery_list", "select_pool_name"), self.pool_name_combo)
- self.addGroup(get_theme_icon("ic_fluent_people_list_20_filled"),
- get_content_name_async("lottery_list", "import_prize_name"), get_content_description_async("lottery_list", "import_prize_name"), self.import_prize_button)
- self.addGroup(get_theme_icon("ic_fluent_rename_20_filled"),
- get_content_name_async("lottery_list", "prize_setting"), get_content_description_async("lottery_list", "prize_setting"), self.prize_setting_button)
- self.addGroup(get_theme_icon("ic_fluent_person_board_20_filled"),
- get_content_name_async("lottery_list", "prize_weight_setting"), get_content_description_async("lottery_list", "prize_weight_setting"), self.prize_weight_setting_button)
- self.addGroup(get_theme_icon("ic_fluent_people_list_20_filled"),
- get_content_name_async("lottery_list", "export_prize_name"), get_content_description_async("lottery_list", "export_prize_name"), self.export_prize_button)
-
+ self.addGroup(
+ get_theme_icon("ic_fluent_slide_text_edit_20_filled"),
+ get_content_name_async("lottery_list", "set_pool_name"),
+ get_content_description_async("lottery_list", "set_pool_name"),
+ self.pool_name_button,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_class_20_filled"),
+ get_content_name_async("lottery_list", "select_pool_name"),
+ get_content_description_async("lottery_list", "select_pool_name"),
+ self.pool_name_combo,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_people_list_20_filled"),
+ get_content_name_async("lottery_list", "import_prize_name"),
+ get_content_description_async("lottery_list", "import_prize_name"),
+ self.import_prize_button,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_rename_20_filled"),
+ get_content_name_async("lottery_list", "prize_setting"),
+ get_content_description_async("lottery_list", "prize_setting"),
+ self.prize_setting_button,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_person_board_20_filled"),
+ get_content_name_async("lottery_list", "prize_weight_setting"),
+ get_content_description_async("lottery_list", "prize_weight_setting"),
+ self.prize_weight_setting_button,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_people_list_20_filled"),
+ get_content_name_async("lottery_list", "export_prize_name"),
+ get_content_description_async("lottery_list", "export_prize_name"),
+ self.export_prize_button,
+ )
+
# 设置文件系统监视器
self.setup_file_watcher()
@@ -80,25 +121,25 @@ def setup_file_watcher(self):
"""设置文件系统监视器,监控奖池名单文件夹的变化"""
# 获取奖池名单文件夹路径
lottery_list_dir = get_path("app/resources/list/lottery_list")
-
+
# 确保目录存在
if not lottery_list_dir.exists():
logger.warning(f"奖池名单文件夹不存在: {lottery_list_dir}")
return
-
+
# 创建文件系统监视器
self.file_watcher = QFileSystemWatcher()
-
+
# 监视目录
self.file_watcher.addPath(str(lottery_list_dir))
-
+
# 连接信号
self.file_watcher.directoryChanged.connect(self.on_directory_changed)
# logger.debug(f"已设置文件监视器,监控目录: {lottery_list_dir}")
def on_directory_changed(self, path):
"""当目录内容发生变化时调用此方法
-
+
Args:
path: 发生变化的目录路径
"""
@@ -110,20 +151,22 @@ def refresh_pool_list(self):
"""刷新奖池下拉框列表"""
# 保存当前选中的奖池名称
current_pool_name = self.pool_name_combo.currentText()
-
+
# 获取最新的奖池列表
pool_list = get_pool_name_list()
-
+
# 清空并重新添加奖池列表
self.pool_name_combo.clear()
self.pool_name_combo.addItems(pool_list)
-
+
# 尝试恢复之前选中的奖池
if current_pool_name and current_pool_name in pool_list:
index = pool_list.index(current_pool_name)
self.pool_name_combo.setCurrentIndex(index)
elif not pool_list:
self.pool_name_combo.setCurrentIndex(-1)
- self.pool_name_combo.setPlaceholderText(get_content_name_async("lottery_list", "select_pool_name"))
-
+ self.pool_name_combo.setPlaceholderText(
+ get_content_name_async("lottery_list", "select_pool_name")
+ )
+
# logger.debug(f"奖池列表已刷新,共 {len(pool_list)} 个奖池")
diff --git a/app/view/settings/list_management/lottery_table.py b/app/view/settings/list_management/lottery_table.py
index 5ceee0a1..31d9a6b7 100644
--- a/app/view/settings/list_management/lottery_table.py
+++ b/app/view/settings/list_management/lottery_table.py
@@ -2,17 +2,13 @@
# 导入库
# ==================================================
import json
-import os
-import sys
-import subprocess
-import pandas as pd
from collections import OrderedDict
from loguru import logger
-from PyQt6.QtWidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from PySide6.QtNetwork import *
from qfluentwidgets import *
from app.tools.variable import *
@@ -23,14 +19,15 @@
from app.Language.obtain_language import *
from app.tools.list import *
+
# ==================================================
# 抽奖名单表格
# ==================================================
class lottery_table(GroupHeaderCardWidget):
"""抽奖名单表格卡片"""
-
- refresh_signal = pyqtSignal()
-
+
+ refresh_signal = Signal()
+
def __init__(self, parent=None):
super().__init__(parent)
self.parent = parent
@@ -38,35 +35,49 @@ def __init__(self, parent=None):
self.setBorderRadius(8)
# 创建抽奖名单选择区域
QTimer.singleShot(APPLY_DELAY, self.create_lottery_selection)
-
+
# 创建表格区域
QTimer.singleShot(APPLY_DELAY, self.create_table)
-
+
# 初始化抽奖名单列表
QTimer.singleShot(APPLY_DELAY, self.refresh_lottery_list)
-
+
# 设置文件系统监视器
QTimer.singleShot(APPLY_DELAY, self.setup_file_watcher)
-
+
# 初始化数据
QTimer.singleShot(APPLY_DELAY, self.refresh_data)
-
+
# 连接信号
self.refresh_signal.connect(self.refresh_data)
-
+
def create_lottery_selection(self):
"""创建抽奖名单选择区域"""
self.lottery_comboBox = ComboBox()
- self.lottery_comboBox.setCurrentIndex(readme_settings_async("lottery_table", "select_pool_name"))
+ self.lottery_comboBox.setCurrentIndex(
+ readme_settings_async("lottery_table", "select_pool_name")
+ )
if not get_pool_name_list():
self.lottery_comboBox.setCurrentIndex(-1)
- self.lottery_comboBox.setPlaceholderText(get_content_name_async("lottery_table", "select_pool_name"))
- self.lottery_comboBox.currentIndexChanged.connect(lambda: update_settings("lottery_table", "select_pool_name", self.lottery_comboBox.currentIndex()))
+ self.lottery_comboBox.setPlaceholderText(
+ get_content_name_async("lottery_table", "select_pool_name")
+ )
+ self.lottery_comboBox.currentIndexChanged.connect(
+ lambda: update_settings(
+ "lottery_table",
+ "select_pool_name",
+ self.lottery_comboBox.currentIndex(),
+ )
+ )
self.lottery_comboBox.currentTextChanged.connect(self.refresh_data)
- self.addGroup(get_theme_icon("ic_fluent_class_20_filled"),
- get_content_name_async("lottery_table", "select_pool_name"), get_content_description_async("lottery_table", "select_pool_name"), self.lottery_comboBox)
-
+ self.addGroup(
+ get_theme_icon("ic_fluent_class_20_filled"),
+ get_content_name_async("lottery_table", "select_pool_name"),
+ get_content_description_async("lottery_table", "select_pool_name"),
+ self.lottery_comboBox,
+ )
+
def create_table(self):
"""创建表格区域"""
# 创建表格
@@ -81,173 +92,195 @@ def create_table(self):
self.table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
self.table.verticalHeader().hide()
- self.table.setHorizontalHeaderLabels(get_content_name_async("lottery_table", "HeaderLabels"))
+ self.table.setHorizontalHeaderLabels(
+ get_content_name_async("lottery_table", "HeaderLabels")
+ )
self.table.horizontalHeader().resizeSection(0, 80)
# 设置表格属性
- self.table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents)
+ self.table.horizontalHeader().setSectionResizeMode(
+ 0, QHeaderView.ResizeMode.ResizeToContents
+ )
for i in range(1, 4):
- self.table.horizontalHeader().setSectionResizeMode(i, QHeaderView.ResizeMode.Stretch)
+ self.table.horizontalHeader().setSectionResizeMode(
+ i, QHeaderView.ResizeMode.Stretch
+ )
for i in range(self.table.columnCount()):
- self.table.horizontalHeader().setDefaultAlignment(Qt.AlignmentFlag.AlignCenter)
+ self.table.horizontalHeader().setDefaultAlignment(
+ Qt.AlignmentFlag.AlignCenter
+ )
# 连接单元格修改信号
self.table.cellChanged.connect(self.save_table_data)
self.layout().addWidget(self.table)
-
+
def setup_file_watcher(self):
"""设置文件系统监视器,监控班级名单文件夹的变化"""
# 获取抽奖名单文件夹路径
lottery_list_dir = get_path("app/resources/list/lottery_list")
-
+
# 确保目录存在
if not lottery_list_dir.exists():
logger.warning(f"奖池文件夹不存在: {lottery_list_dir}")
return
-
+
# 创建文件系统监视器
self.file_watcher = QFileSystemWatcher()
-
+
# 监视目录
self.file_watcher.addPath(str(lottery_list_dir))
-
+
# 连接信号
self.file_watcher.directoryChanged.connect(self.on_directory_changed)
# logger.debug(f"已设置文件监视器,监控目录: {lottery_list_dir}")
def on_directory_changed(self, path):
"""当目录内容发生变化时调用此方法
-
+
Args:
path: 发生变化的目录路径
"""
# logger.debug(f"检测到目录变化: {path}")
# 延迟刷新,避免文件操作未完成
QTimer.singleShot(1000, self.refresh_lottery_list)
-
+
def refresh_lottery_list(self):
"""刷新抽奖名单下拉框列表"""
# 保存当前选中的抽奖名单名称
current_lottery_name = self.lottery_comboBox.currentText()
-
+
# 获取最新的抽奖名单列表
lottery_list = get_pool_name_list()
-
+
# 清空并重新添加抽奖名单列表
self.lottery_comboBox.clear()
self.lottery_comboBox.addItems(lottery_list)
-
+
# 尝试恢复之前选中的抽奖池
if current_lottery_name and current_lottery_name in lottery_list:
index = lottery_list.index(current_lottery_name)
self.lottery_comboBox.setCurrentIndex(index)
elif not lottery_list:
self.lottery_comboBox.setCurrentIndex(-1)
- self.lottery_comboBox.setPlaceholderText(get_content_name_async("lottery_list", "select_pool_name"))
-
+ self.lottery_comboBox.setPlaceholderText(
+ get_content_name_async("lottery_list", "select_pool_name")
+ )
+
# logger.debug(f"抽奖名单列表已刷新,共 {len(lottery_list)} 个抽奖名单")
# 只有在表格已经创建时才刷新数据
- if hasattr(self, 'table'):
+ if hasattr(self, "table"):
self.refresh_data()
-
+
def refresh_data(self):
"""刷新抽奖名单数据"""
# 确保表格已经创建
- if not hasattr(self, 'table'):
+ if not hasattr(self, "table"):
return
-
+
pool_name = self.lottery_comboBox.currentText()
if not pool_name:
self.table.setRowCount(0)
return
-
+
# 临时阻止信号,避免初始化时触发保存操作
self.table.blockSignals(True)
-
+
try:
# 获取抽奖池数据
pool = get_pool_data(pool_name)
if not pool:
self.table.setRowCount(0)
return
-
+
# 设置表格行数
self.table.setRowCount(len(pool))
-
+
# 填充表格数据
for row, item in enumerate(pool):
# 是否存在勾选框
checkbox_item = QTableWidgetItem()
- checkbox_item.setCheckState(Qt.CheckState.Checked if item.get('exist', True) else Qt.CheckState.Unchecked)
+ checkbox_item.setCheckState(
+ Qt.CheckState.Checked
+ if item.get("exist", True)
+ else Qt.CheckState.Unchecked
+ )
checkbox_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.table.setItem(row, 0, checkbox_item)
-
+
# 奖品ID
- id_item = QTableWidgetItem(str(item.get('id', row + 1)))
- id_item.setFlags(id_item.flags() & ~Qt.ItemFlag.ItemIsEditable) # 学号不可编辑
+ id_item = QTableWidgetItem(str(item.get("id", row + 1)))
+ id_item.setFlags(
+ id_item.flags() & ~Qt.ItemFlag.ItemIsEditable
+ ) # 学号不可编辑
id_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.table.setItem(row, 1, id_item)
-
+
# 奖品名称
- name_item = QTableWidgetItem(item.get('name', ''))
+ name_item = QTableWidgetItem(item.get("name", ""))
name_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.table.setItem(row, 2, name_item)
-
+
# 奖品权重
- weight_item = QTableWidgetItem(str(item.get('weight', 1)))
+ weight_item = QTableWidgetItem(str(item.get("weight", 1)))
weight_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.table.setItem(row, 3, weight_item)
-
+
# 调整列宽
self.table.horizontalHeader().resizeSection(0, 80)
- self.table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents)
+ self.table.horizontalHeader().setSectionResizeMode(
+ 0, QHeaderView.ResizeMode.ResizeToContents
+ )
for i in range(1, 4):
- self.table.horizontalHeader().setSectionResizeMode(i, QHeaderView.ResizeMode.Stretch)
-
+ self.table.horizontalHeader().setSectionResizeMode(
+ i, QHeaderView.ResizeMode.Stretch
+ )
+
except Exception as e:
logger.error(f"刷新抽奖名单表格数据失败: {str(e)}")
finally:
# 恢复信号
self.table.blockSignals(False)
-
+
def save_table_data(self, row, col):
"""保存表格编辑的数据"""
pool_name = self.lottery_comboBox.currentText()
if not pool_name:
return
-
+
# 获取当前单元格
item = self.table.item(row, col)
if not item:
return
-
+
# 获取当前行的奖品ID和名称
id_item = self.table.item(row, 1)
name_item = self.table.item(row, 2)
if not id_item or not name_item:
return
item_id = id_item.text()
- item_name = name_item.text()
-
+ item_name = name_item.text()
+
# 加载当前抽奖池数据
- pool_file = get_path("app/resources/list/lottery_list") / f'{pool_name}.json'
+ pool_file = get_path("app/resources/list/lottery_list") / f"{pool_name}.json"
try:
- with open_file(pool_file, 'r', encoding='utf-8') as f:
+ with open_file(pool_file, "r", encoding="utf-8") as f:
pool_data = json.load(f, object_pairs_hook=OrderedDict)
except Exception as e:
logger.error(f"加载抽奖池数据失败: {str(e)}")
return
-
+
# 通过奖品ID找到对应的奖品键
matched_key = None
for key, value in pool_data.items():
- stored_id = value.get('id')
- if str(stored_id).lstrip('0') == str(item_id).lstrip('0') or str(stored_id) == str(item_id):
+ stored_id = value.get("id")
+ if str(stored_id).lstrip("0") == str(item_id).lstrip("0") or str(
+ stored_id
+ ) == str(item_id):
matched_key = key
break
-
+
if not matched_key:
logger.error(f"未找到奖品ID为 {item_id} 的奖品,奖品名称: {item_name}")
return
-
+
# 根据列索引更新相应的字段
new_value = item.text()
if col == 2: # 奖品名称列
@@ -261,31 +294,33 @@ def save_table_data(self, row, col):
new_pool_data[key] = value
pool_data = new_pool_data
elif col == 3: # 奖品权重列
- pool_data[matched_key]['weight'] = int(new_value)
+ pool_data[matched_key]["weight"] = int(new_value)
elif col == 0: # "存在"勾选框列
checkbox_item = self.table.item(row, 0)
if checkbox_item:
is_checked = checkbox_item.checkState() == Qt.CheckState.Checked
- pool_data[matched_key]['exist'] = is_checked
-
+ pool_data[matched_key]["exist"] = is_checked
+
# 保存更新后的数据
try:
# 暂时禁用文件监视器,避免保存时触发刷新循环
- if hasattr(self, 'file_watcher'):
+ if hasattr(self, "file_watcher"):
self.file_watcher.removePath(str(pool_file))
-
- with open_file(pool_file, 'w', encoding='utf-8') as f:
+
+ with open_file(pool_file, "w", encoding="utf-8") as f:
json.dump(pool_data, f, ensure_ascii=False, indent=4)
# logger.debug(f"抽奖池数据更新成功: {pool_name}")
-
+
# 保存成功后设置列宽
self.table.blockSignals(True)
for i in range(1, 4):
- self.table.horizontalHeader().setSectionResizeMode(i, QHeaderView.ResizeMode.Stretch)
+ self.table.horizontalHeader().setSectionResizeMode(
+ i, QHeaderView.ResizeMode.Stretch
+ )
self.table.blockSignals(False)
-
+
# 重新启用文件监视器
- if hasattr(self, 'file_watcher'):
+ if hasattr(self, "file_watcher"):
self.file_watcher.addPath(str(pool_file))
except Exception as e:
logger.error(f"保存抽奖池数据失败: {str(e)}")
@@ -297,11 +332,13 @@ def save_table_data(self, row, col):
else:
original_value = ""
if matched_key:
- original_value = pool_data[matched_key]['weight'] if col == 3 else ""
+ original_value = (
+ pool_data[matched_key]["weight"] if col == 3 else ""
+ )
item.setText(str(original_value))
item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.table.blockSignals(False) # 恢复信号
-
+
# 即使保存失败也要重新启用文件监视器
- if hasattr(self, 'file_watcher'):
+ if hasattr(self, "file_watcher"):
self.file_watcher.addPath(str(pool_file))
diff --git a/app/view/settings/list_management/roll_call_list.py b/app/view/settings/list_management/roll_call_list.py
index 55691abd..c35ea3a2 100644
--- a/app/view/settings/list_management/roll_call_list.py
+++ b/app/view/settings/list_management/roll_call_list.py
@@ -1,16 +1,12 @@
# ==================================================
# 导入库
# ==================================================
-import json
-import os
-import sys
-import subprocess
from loguru import logger
-from PyQt6.QtWidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from PySide6.QtNetwork import *
from qfluentwidgets import *
from app.tools.variable import *
@@ -19,8 +15,12 @@
from app.tools.settings_default import *
from app.tools.settings_access import *
from app.Language.obtain_language import *
+from app.tools.config import *
from app.tools.list import *
+from app.page_building.another_window import *
+
+
# ==================================================
# 点名名单
# ==================================================
@@ -31,80 +31,227 @@ def __init__(self, parent=None):
self.setBorderRadius(8)
# 设置班级名称按钮
- self.class_name_button = PushButton(get_content_name_async("roll_call_list", "set_class_name"))
+ self.class_name_button = PushButton(
+ get_content_name_async("roll_call_list", "set_class_name")
+ )
self.class_name_button.clicked.connect(lambda: self.set_class_name())
# 选择班级下拉框
self.class_name_combo = ComboBox()
self.refresh_class_list() # 初始化班级列表
- self.class_name_combo.setCurrentIndex(readme_settings_async("roll_call_list", "select_class_name"))
if not get_class_name_list():
self.class_name_combo.setCurrentIndex(-1)
- self.class_name_combo.setPlaceholderText(get_content_name_async("roll_call_list", "select_class_name"))
- self.class_name_combo.currentIndexChanged.connect(lambda: update_settings("roll_call_list", "select_class_name", self.class_name_combo.currentIndex()))
+ self.class_name_combo.setPlaceholderText(
+ get_content_name_async("roll_call_list", "select_class_name")
+ )
+ else:
+ self.class_name_combo.setCurrentText(
+ readme_settings_async("roll_call_list", "select_class_name")
+ )
+ self.class_name_combo.currentIndexChanged.connect(
+ lambda: update_settings(
+ "roll_call_list",
+ "select_class_name",
+ self.class_name_combo.currentText(),
+ )
+ )
# 导入学生名单按钮
- self.import_student_button = PushButton(get_content_name_async("roll_call_list", "import_student_name"))
+ self.import_student_button = PushButton(
+ get_content_name_async("roll_call_list", "import_student_name")
+ )
self.import_student_button.clicked.connect(lambda: self.import_student_name())
# 姓名设置按钮
- self.name_setting_button = PushButton(get_content_name_async("roll_call_list", "name_setting"))
+ self.name_setting_button = PushButton(
+ get_content_name_async("roll_call_list", "name_setting")
+ )
self.name_setting_button.clicked.connect(lambda: self.name_setting())
# 性别设置按钮
- self.gender_setting_button = PushButton(get_content_name_async("roll_call_list", "gender_setting"))
+ self.gender_setting_button = PushButton(
+ get_content_name_async("roll_call_list", "gender_setting")
+ )
self.gender_setting_button.clicked.connect(lambda: self.gender_setting())
# 小组设置按钮
- self.group_setting_button = PushButton(get_content_name_async("roll_call_list", "group_setting"))
+ self.group_setting_button = PushButton(
+ get_content_name_async("roll_call_list", "group_setting")
+ )
self.group_setting_button.clicked.connect(lambda: self.group_setting())
# 导出学生名单按钮
- self.export_student_button = PushButton(get_content_name_async("roll_call_list", "export_student_name"))
- self.export_student_button.clicked.connect(lambda: self.export_student_name())
+ self.export_student_button = PushButton(
+ get_content_name_async("roll_call_list", "export_student_name")
+ )
+ self.export_student_button.clicked.connect(lambda: self.export_student_list())
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_slide_text_edit_20_filled"),
- get_content_name_async("roll_call_list", "set_class_name"), get_content_description_async("roll_call_list", "set_class_name"), self.class_name_button)
- self.addGroup(get_theme_icon("ic_fluent_class_20_filled"),
- get_content_name_async("roll_call_list", "select_class_name"), get_content_description_async("roll_call_list", "select_class_name"), self.class_name_combo)
- self.addGroup(get_theme_icon("ic_fluent_people_list_20_filled"),
- get_content_name_async("roll_call_list", "import_student_name"), get_content_description_async("roll_call_list", "import_student_name"), self.import_student_button)
- self.addGroup(get_theme_icon("ic_fluent_rename_20_filled"),
- get_content_name_async("roll_call_list", "name_setting"), get_content_description_async("roll_call_list", "name_setting"), self.name_setting_button)
- self.addGroup(get_theme_icon("ic_fluent_person_board_20_filled"),
- get_content_name_async("roll_call_list", "gender_setting"), get_content_description_async("roll_call_list", "gender_setting"), self.gender_setting_button)
- self.addGroup(get_theme_icon("ic_fluent_tab_group_20_filled"),
- get_content_name_async("roll_call_list", "group_setting"), get_content_description_async("roll_call_list", "group_setting"), self.group_setting_button)
- self.addGroup(get_theme_icon("ic_fluent_people_list_20_filled"),
- get_content_name_async("roll_call_list", "export_student_name"), get_content_description_async("roll_call_list", "export_student_name"), self.export_student_button)
-
+ self.addGroup(
+ get_theme_icon("ic_fluent_slide_text_edit_20_filled"),
+ get_content_name_async("roll_call_list", "set_class_name"),
+ get_content_description_async("roll_call_list", "set_class_name"),
+ self.class_name_button,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_class_20_filled"),
+ get_content_name_async("roll_call_list", "select_class_name"),
+ get_content_description_async("roll_call_list", "select_class_name"),
+ self.class_name_combo,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_people_list_20_filled"),
+ get_content_name_async("roll_call_list", "import_student_name"),
+ get_content_description_async("roll_call_list", "import_student_name"),
+ self.import_student_button,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_rename_20_filled"),
+ get_content_name_async("roll_call_list", "name_setting"),
+ get_content_description_async("roll_call_list", "name_setting"),
+ self.name_setting_button,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_person_board_20_filled"),
+ get_content_name_async("roll_call_list", "gender_setting"),
+ get_content_description_async("roll_call_list", "gender_setting"),
+ self.gender_setting_button,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_tab_group_20_filled"),
+ get_content_name_async("roll_call_list", "group_setting"),
+ get_content_description_async("roll_call_list", "group_setting"),
+ self.group_setting_button,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_people_list_20_filled"),
+ get_content_name_async("roll_call_list", "export_student_name"),
+ get_content_description_async("roll_call_list", "export_student_name"),
+ self.export_student_button,
+ )
+
# 设置文件系统监视器
self.setup_file_watcher()
+ # 班级名称设置
+ def set_class_name(self):
+ create_set_class_name_window()
+ # 显示通知
+ config = NotificationConfig(
+ title="班级名称设置", content="已打开班级名称设置窗口", duration=3000
+ )
+ show_notification(NotificationType.INFO, config, parent=self)
+
+ # 学生名单导入功能
+ def import_student_name(self):
+ create_import_student_name_window()
+ # 显示通知
+ config = NotificationConfig(
+ title="学生名单导入", content="已打开学生名单导入窗口", duration=3000
+ )
+ show_notification(NotificationType.INFO, config, parent=self)
+
+ # 姓名设置
+ def name_setting(self):
+ create_name_setting_window()
+ # 显示通知
+ config = NotificationConfig(
+ title="姓名设置", content="已打开姓名设置窗口", duration=3000
+ )
+ show_notification(NotificationType.INFO, config, parent=self)
+
+ # 性别设置
+ def gender_setting(self):
+ create_gender_setting_window()
+ # 显示通知
+ config = NotificationConfig(
+ title="性别设置", content="已打开性别设置窗口", duration=3000
+ )
+ show_notification(NotificationType.INFO, config, parent=self)
+
+ # 小组设置
+ def group_setting(self):
+ create_group_setting_window()
+ # 显示通知
+ config = NotificationConfig(
+ title="小组设置", content="已打开小组设置窗口", duration=3000
+ )
+ show_notification(NotificationType.INFO, config, parent=self)
+
+ # 学生名单导出功能
+ def export_student_list(self):
+ class_name = self.class_name_combo.currentText()
+ if not class_name:
+ config = NotificationConfig(
+ title="导出失败", content="请先选择要导出的班级", duration=3000
+ )
+ show_notification(NotificationType.WARNING, config, parent=self)
+ return
+
+ file_path, selected_filter = QFileDialog.getSaveFileName(
+ self,
+ "保存学生名单",
+ f"{class_name}_学生名单-SecRandom",
+ "Excel 文件 (*.xlsx);;CSV 文件 (*.csv);;TXT 文件(仅姓名) (*.txt)",
+ )
+
+ if not file_path:
+ return
+
+ export_type = (
+ "excel"
+ if "Excel 文件 (*.xlsx)" in selected_filter
+ else "csv"
+ if "CSV 文件 (*.csv)" in selected_filter
+ else "txt"
+ )
+
+ if export_type == "excel" and not file_path.endswith(".xlsx"):
+ file_path += ".xlsx"
+ elif export_type == "csv" and not file_path.endswith(".csv"):
+ file_path += ".csv"
+ elif export_type == "txt" and not file_path.endswith(".txt"):
+ file_path += ".txt"
+
+ success, message = export_student_data(class_name, file_path, export_type)
+
+ if success:
+ config = NotificationConfig(
+ title="导出成功",
+ content=f"学生名单已导出到: {file_path}",
+ duration=3000,
+ )
+ show_notification(NotificationType.SUCCESS, config, parent=self)
+ logger.info(f"学生名单导出成功: {file_path}")
+ else:
+ config = NotificationConfig(
+ title="导出失败", content=message, duration=3000
+ )
+ show_notification(NotificationType.ERROR, config, parent=self)
+ logger.error(f"学生名单导出失败: {message}")
+
def setup_file_watcher(self):
"""设置文件系统监视器,监控班级名单文件夹的变化"""
- # 获取班级名单文件夹路径
roll_call_list_dir = get_path("app/resources/list/roll_call_list")
-
+
# 确保目录存在
if not roll_call_list_dir.exists():
logger.warning(f"班级名单文件夹不存在: {roll_call_list_dir}")
return
-
+
# 创建文件系统监视器
self.file_watcher = QFileSystemWatcher()
-
+
# 监视目录
self.file_watcher.addPath(str(roll_call_list_dir))
-
+
# 连接信号
self.file_watcher.directoryChanged.connect(self.on_directory_changed)
# logger.debug(f"已设置文件监视器,监控目录: {roll_call_list_dir}")
def on_directory_changed(self, path):
"""当目录内容发生变化时调用此方法
-
+
Args:
path: 发生变化的目录路径
"""
@@ -116,20 +263,22 @@ def refresh_class_list(self):
"""刷新班级下拉框列表"""
# 保存当前选中的班级名称
current_class_name = self.class_name_combo.currentText()
-
+
# 获取最新的班级列表
class_list = get_class_name_list()
-
+
# 清空并重新添加班级列表
self.class_name_combo.clear()
self.class_name_combo.addItems(class_list)
-
+
# 尝试恢复之前选中的班级
if current_class_name and current_class_name in class_list:
index = class_list.index(current_class_name)
self.class_name_combo.setCurrentIndex(index)
elif not class_list:
self.class_name_combo.setCurrentIndex(-1)
- self.class_name_combo.setPlaceholderText(get_content_name_async("roll_call_list", "select_class_name"))
-
- # logger.debug(f"班级列表已刷新,共 {len(class_list)} 个班级")
\ No newline at end of file
+ self.class_name_combo.setPlaceholderText(
+ get_content_name_async("roll_call_list", "select_class_name")
+ )
+
+ # logger.debug(f"班级列表已刷新,共 {len(class_list)} 个班级")
diff --git a/app/view/settings/list_management/roll_call_table.py b/app/view/settings/list_management/roll_call_table.py
index 4487c24d..837676d5 100644
--- a/app/view/settings/list_management/roll_call_table.py
+++ b/app/view/settings/list_management/roll_call_table.py
@@ -2,17 +2,13 @@
# 导入库
# ==================================================
import json
-import os
-import sys
-import subprocess
-import pandas as pd
from collections import OrderedDict
from loguru import logger
-from PyQt6.QtWidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from PySide6.QtNetwork import *
from qfluentwidgets import *
from app.tools.variable import *
@@ -27,11 +23,12 @@
# 点名名单表格
# ==================================================
+
class roll_call_table(GroupHeaderCardWidget):
"""点名名单表格卡片"""
-
- refresh_signal = pyqtSignal()
-
+
+ refresh_signal = Signal()
+
def __init__(self, parent=None):
super().__init__(parent)
self.parent = parent
@@ -39,35 +36,49 @@ def __init__(self, parent=None):
self.setBorderRadius(8)
# 创建班级选择区域
QTimer.singleShot(APPLY_DELAY, self.create_class_selection)
-
+
# 创建表格区域
QTimer.singleShot(APPLY_DELAY, self.create_table)
-
+
# 初始化班级列表
QTimer.singleShot(APPLY_DELAY, self.refresh_class_list)
-
+
# 设置文件系统监视器
QTimer.singleShot(APPLY_DELAY, self.setup_file_watcher)
-
+
# 初始化数据
QTimer.singleShot(APPLY_DELAY, self.refresh_data)
-
+
# 连接信号
self.refresh_signal.connect(self.refresh_data)
-
+
def create_class_selection(self):
"""创建班级选择区域"""
self.class_comboBox = ComboBox()
- self.class_comboBox.setCurrentIndex(readme_settings_async("roll_call_table", "select_class_name"))
+ self.class_comboBox.setCurrentIndex(
+ readme_settings_async("roll_call_table", "select_class_name")
+ )
if not get_class_name_list():
self.class_comboBox.setCurrentIndex(-1)
- self.class_comboBox.setPlaceholderText(get_content_name_async("roll_call_table", "select_class_name"))
- self.class_comboBox.currentIndexChanged.connect(lambda: update_settings("roll_call_table", "select_class_name", self.class_comboBox.currentIndex()))
+ self.class_comboBox.setPlaceholderText(
+ get_content_name_async("roll_call_table", "select_class_name")
+ )
+ self.class_comboBox.currentIndexChanged.connect(
+ lambda: update_settings(
+ "roll_call_table",
+ "select_class_name",
+ self.class_comboBox.currentIndex(),
+ )
+ )
self.class_comboBox.currentTextChanged.connect(self.refresh_data)
- self.addGroup(get_theme_icon("ic_fluent_class_20_filled"),
- get_content_name_async("roll_call_table", "select_class_name"), get_content_description_async("roll_call_table", "select_class_name"), self.class_comboBox)
-
+ self.addGroup(
+ get_theme_icon("ic_fluent_class_20_filled"),
+ get_content_name_async("roll_call_table", "select_class_name"),
+ get_content_description_async("roll_call_table", "select_class_name"),
+ self.class_comboBox,
+ )
+
def create_table(self):
"""创建表格区域"""
# 创建表格
@@ -82,149 +93,169 @@ def create_table(self):
self.table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
self.table.verticalHeader().hide()
- self.table.setHorizontalHeaderLabels(get_content_name_async("roll_call_table", "HeaderLabels"))
+ self.table.setHorizontalHeaderLabels(
+ get_content_name_async("roll_call_table", "HeaderLabels")
+ )
self.table.horizontalHeader().resizeSection(0, 80)
# 设置表格属性
- self.table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents)
+ self.table.horizontalHeader().setSectionResizeMode(
+ 0, QHeaderView.ResizeMode.ResizeToContents
+ )
for i in range(1, 5):
- self.table.horizontalHeader().setSectionResizeMode(i, QHeaderView.ResizeMode.Stretch)
+ self.table.horizontalHeader().setSectionResizeMode(
+ i, QHeaderView.ResizeMode.Stretch
+ )
for i in range(self.table.columnCount()):
- self.table.horizontalHeader().setDefaultAlignment(Qt.AlignmentFlag.AlignCenter)
+ self.table.horizontalHeader().setDefaultAlignment(
+ Qt.AlignmentFlag.AlignCenter
+ )
# 连接单元格修改信号
self.table.cellChanged.connect(self.save_table_data)
self.layout().addWidget(self.table)
-
+
def setup_file_watcher(self):
"""设置文件系统监视器,监控班级名单文件夹的变化"""
# 获取班级名单文件夹路径
roll_call_list_dir = get_path("app/resources/list/roll_call_list")
-
+
# 确保目录存在
if not roll_call_list_dir.exists():
logger.warning(f"班级名单文件夹不存在: {roll_call_list_dir}")
return
-
+
# 创建文件系统监视器
self.file_watcher = QFileSystemWatcher()
-
+
# 监视目录
self.file_watcher.addPath(str(roll_call_list_dir))
-
+
# 连接信号
self.file_watcher.directoryChanged.connect(self.on_directory_changed)
# logger.debug(f"已设置文件监视器,监控目录: {roll_call_list_dir}")
def on_directory_changed(self, path):
"""当目录内容发生变化时调用此方法
-
+
Args:
path: 发生变化的目录路径
"""
# logger.debug(f"检测到目录变化: {path}")
# 延迟刷新,避免文件操作未完成
QTimer.singleShot(1000, self.refresh_class_list)
-
+
def refresh_class_list(self):
"""刷新班级下拉框列表"""
# 保存当前选中的班级名称
current_class_name = self.class_comboBox.currentText()
-
+
# 获取最新的班级列表
class_list = get_class_name_list()
-
+
# 清空并重新添加班级列表
self.class_comboBox.clear()
self.class_comboBox.addItems(class_list)
-
+
# 尝试恢复之前选中的班级
if current_class_name and current_class_name in class_list:
index = class_list.index(current_class_name)
self.class_comboBox.setCurrentIndex(index)
elif not class_list:
self.class_comboBox.setCurrentIndex(-1)
- self.class_comboBox.setPlaceholderText(get_content_name_async("roll_call_list", "select_class_name"))
-
+ self.class_comboBox.setPlaceholderText(
+ get_content_name_async("roll_call_list", "select_class_name")
+ )
+
# logger.debug(f"班级列表已刷新,共 {len(class_list)} 个班级")
# 只有在表格已经创建时才刷新数据
- if hasattr(self, 'table'):
+ if hasattr(self, "table"):
self.refresh_data()
-
+
def refresh_data(self):
"""刷新表格数据"""
# 确保表格已经创建
- if not hasattr(self, 'table'):
+ if not hasattr(self, "table"):
return
-
+
class_name = self.class_comboBox.currentText()
if not class_name:
self.table.setRowCount(0)
return
-
+
# 临时阻止信号,避免初始化时触发保存操作
self.table.blockSignals(True)
-
+
try:
# 获取学生数据
students = get_student_list(class_name)
if not students:
self.table.setRowCount(0)
return
-
+
# 设置表格行数
self.table.setRowCount(len(students))
-
+
# 填充表格数据
for row, student in enumerate(students):
# 是否在班级勾选框
checkbox_item = QTableWidgetItem()
- checkbox_item.setCheckState(Qt.CheckState.Checked if student.get('exist', True) else Qt.CheckState.Unchecked)
+ checkbox_item.setCheckState(
+ Qt.CheckState.Checked
+ if student.get("exist", True)
+ else Qt.CheckState.Unchecked
+ )
checkbox_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.table.setItem(row, 0, checkbox_item)
-
+
# 学号
- id_item = QTableWidgetItem(str(student.get('id', row + 1)))
- id_item.setFlags(id_item.flags() & ~Qt.ItemFlag.ItemIsEditable) # 学号不可编辑
+ id_item = QTableWidgetItem(str(student.get("id", row + 1)))
+ id_item.setFlags(
+ id_item.flags() & ~Qt.ItemFlag.ItemIsEditable
+ ) # 学号不可编辑
id_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.table.setItem(row, 1, id_item)
-
+
# 姓名
- name_item = QTableWidgetItem(student.get('name', ''))
+ name_item = QTableWidgetItem(student.get("name", ""))
name_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.table.setItem(row, 2, name_item)
-
+
# 性别
- gender_item = QTableWidgetItem(student.get('gender', ''))
+ gender_item = QTableWidgetItem(student.get("gender", ""))
gender_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.table.setItem(row, 3, gender_item)
-
+
# 小组
- group_item = QTableWidgetItem(student.get('group', ''))
+ group_item = QTableWidgetItem(student.get("group", ""))
group_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.table.setItem(row, 4, group_item)
-
+
# 调整列宽
self.table.horizontalHeader().resizeSection(0, 80)
- self.table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents)
+ self.table.horizontalHeader().setSectionResizeMode(
+ 0, QHeaderView.ResizeMode.ResizeToContents
+ )
for i in range(1, 5):
- self.table.horizontalHeader().setSectionResizeMode(i, QHeaderView.ResizeMode.Stretch)
-
+ self.table.horizontalHeader().setSectionResizeMode(
+ i, QHeaderView.ResizeMode.Stretch
+ )
+
except Exception as e:
logger.error(f"刷新表格数据失败: {str(e)}")
finally:
# 恢复信号
self.table.blockSignals(False)
-
+
def save_table_data(self, row, col):
"""保存表格编辑的数据"""
class_name = self.class_comboBox.currentText()
if not class_name:
return
-
+
# 获取当前单元格
item = self.table.item(row, col)
if not item:
return
-
+
# 获取当前行的学号和姓名
id_item = self.table.item(row, 1)
name_item = self.table.item(row, 2)
@@ -232,29 +263,31 @@ def save_table_data(self, row, col):
return
student_id = id_item.text()
student_name = name_item.text()
-
+
# 加载当前班级的学生数据
roll_call_list_dir = get_path("app/resources/list/roll_call_list")
- student_file = roll_call_list_dir / f'{class_name}.json'
+ student_file = roll_call_list_dir / f"{class_name}.json"
try:
- with open_file(student_file, 'r', encoding='utf-8') as f:
+ with open_file(student_file, "r", encoding="utf-8") as f:
student_data = json.load(f, object_pairs_hook=OrderedDict)
except Exception as e:
logger.error(f"加载学生数据失败: {str(e)}")
return
-
+
# 通过学号找到对应的学生键
matched_key = None
for key, value in student_data.items():
- stored_id = value.get('id')
- if str(stored_id).lstrip('0') == str(student_id).lstrip('0') or str(stored_id) == str(student_id):
+ stored_id = value.get("id")
+ if str(stored_id).lstrip("0") == str(student_id).lstrip("0") or str(
+ stored_id
+ ) == str(student_id):
matched_key = key
break
-
+
if not matched_key:
logger.error(f"未找到学号为 {student_id} 的学生,学生姓名: {student_name}")
return
-
+
# 根据列索引更新相应的字段
new_value = item.text()
if col == 2: # 姓名列
@@ -268,33 +301,35 @@ def save_table_data(self, row, col):
new_student_data[key] = value
student_data = new_student_data
elif col == 3: # 性别列
- student_data[matched_key]['gender'] = new_value
+ student_data[matched_key]["gender"] = new_value
elif col == 4: # 小组列
- student_data[matched_key]['group'] = new_value
+ student_data[matched_key]["group"] = new_value
elif col == 0: # "是否在班级"勾选框列
checkbox_item = self.table.item(row, 0)
if checkbox_item:
is_checked = checkbox_item.checkState() == Qt.CheckState.Checked
- student_data[matched_key]['exist'] = is_checked
-
+ student_data[matched_key]["exist"] = is_checked
+
# 保存更新后的数据
try:
# 暂时禁用文件监视器,避免保存时触发刷新循环
- if hasattr(self, 'file_watcher'):
+ if hasattr(self, "file_watcher"):
self.file_watcher.removePath(str(roll_call_list_dir))
-
- with open_file(student_file, 'w', encoding='utf-8') as f:
+
+ with open_file(student_file, "w", encoding="utf-8") as f:
json.dump(student_data, f, ensure_ascii=False, indent=4)
# logger.debug(f"学生数据更新成功: {student_name}")
-
+
# 保存成功后设置列宽
self.table.blockSignals(True)
for i in range(1, 5):
- self.table.horizontalHeader().setSectionResizeMode(i, QHeaderView.ResizeMode.Stretch)
+ self.table.horizontalHeader().setSectionResizeMode(
+ i, QHeaderView.ResizeMode.Stretch
+ )
self.table.blockSignals(False)
-
+
# 重新启用文件监视器
- if hasattr(self, 'file_watcher'):
+ if hasattr(self, "file_watcher"):
self.file_watcher.addPath(str(roll_call_list_dir))
except Exception as e:
logger.error(f"保存学生数据失败: {str(e)}")
@@ -306,11 +341,17 @@ def save_table_data(self, row, col):
else:
original_value = ""
if matched_key:
- original_value = student_data[matched_key]['gender'] if col == 3 else student_data[matched_key]['group'] if col == 4 else ""
+ original_value = (
+ student_data[matched_key]["gender"]
+ if col == 3
+ else student_data[matched_key]["group"]
+ if col == 4
+ else ""
+ )
item.setText(str(original_value))
item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.table.blockSignals(False) # 恢复信号
-
+
# 即使保存失败也要重新启用文件监视器
- if hasattr(self, 'file_watcher'):
+ if hasattr(self, "file_watcher"):
self.file_watcher.addPath(str(roll_call_list_dir))
diff --git a/app/view/settings/more_settings/__init__.py b/app/view/settings/more_settings/__init__.py
new file mode 100644
index 00000000..7da50c51
--- /dev/null
+++ b/app/view/settings/more_settings/__init__.py
@@ -0,0 +1 @@
+"""Additional settings pages."""
diff --git a/app/view/settings/more_settings/advanced_settings.py b/app/view/settings/more_settings/advanced_settings.py
index 0317fd21..30d38e25 100644
--- a/app/view/settings/more_settings/advanced_settings.py
+++ b/app/view/settings/more_settings/advanced_settings.py
@@ -1,16 +1,11 @@
# ==================================================
# 导入库
# ==================================================
-import json
-import os
-import sys
-import subprocess
-from loguru import logger
-from PyQt6.QtWidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from PySide6.QtNetwork import *
from qfluentwidgets import *
from app.tools.variable import *
@@ -20,6 +15,7 @@
from app.tools.settings_access import *
from app.Language.obtain_language import *
+
# ==================================================
# 进阶设置
# ==================================================
@@ -44,65 +40,173 @@ def __init__(self, parent=None):
# 总抽取次数是否纳入计算
self.fair_draw_switch = SwitchButton()
- self.fair_draw_switch.setOffText(get_content_switchbutton_name_async("advanced_settings", "fair_draw", "disable"))
- self.fair_draw_switch.setOnText(get_content_switchbutton_name_async("advanced_settings", "fair_draw", "enable"))
- self.fair_draw_switch.setChecked(readme_settings_async("advanced_settings", "fair_draw"))
- self.fair_draw_switch.checkedChanged.connect(lambda: update_settings("advanced_settings", "fair_draw", self.fair_draw_switch.isChecked()))
+ self.fair_draw_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "advanced_settings", "fair_draw", "disable"
+ )
+ )
+ self.fair_draw_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "advanced_settings", "fair_draw", "enable"
+ )
+ )
+ self.fair_draw_switch.setChecked(
+ readme_settings_async("advanced_settings", "fair_draw")
+ )
+ self.fair_draw_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "advanced_settings", "fair_draw", self.fair_draw_switch.isChecked()
+ )
+ )
# 抽小组次数是否纳入计算
self.fair_draw_group_switch = SwitchButton()
- self.fair_draw_group_switch.setOffText(get_content_switchbutton_name_async("advanced_settings", "fair_draw_group", "disable"))
- self.fair_draw_group_switch.setOnText(get_content_switchbutton_name_async("advanced_settings", "fair_draw_group", "enable"))
- self.fair_draw_group_switch.setChecked(readme_settings_async("advanced_settings", "fair_draw_group"))
- self.fair_draw_group_switch.checkedChanged.connect(lambda: update_settings("advanced_settings", "fair_draw_group", self.fair_draw_group_switch.isChecked()))
+ self.fair_draw_group_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "advanced_settings", "fair_draw_group", "disable"
+ )
+ )
+ self.fair_draw_group_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "advanced_settings", "fair_draw_group", "enable"
+ )
+ )
+ self.fair_draw_group_switch.setChecked(
+ readme_settings_async("advanced_settings", "fair_draw_group")
+ )
+ self.fair_draw_group_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "advanced_settings",
+ "fair_draw_group",
+ self.fair_draw_group_switch.isChecked(),
+ )
+ )
# 抽性别次数是否纳入计算
self.fair_draw_gender_switch = SwitchButton()
- self.fair_draw_gender_switch.setOffText(get_content_switchbutton_name_async("advanced_settings", "fair_draw_gender", "disable"))
- self.fair_draw_gender_switch.setOnText(get_content_switchbutton_name_async("advanced_settings", "fair_draw_gender", "enable"))
- self.fair_draw_gender_switch.setChecked(readme_settings_async("advanced_settings", "fair_draw_gender"))
- self.fair_draw_gender_switch.checkedChanged.connect(lambda: update_settings("advanced_settings", "fair_draw_gender", self.fair_draw_gender_switch.isChecked()))
+ self.fair_draw_gender_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "advanced_settings", "fair_draw_gender", "disable"
+ )
+ )
+ self.fair_draw_gender_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "advanced_settings", "fair_draw_gender", "enable"
+ )
+ )
+ self.fair_draw_gender_switch.setChecked(
+ readme_settings_async("advanced_settings", "fair_draw_gender")
+ )
+ self.fair_draw_gender_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "advanced_settings",
+ "fair_draw_gender",
+ self.fair_draw_gender_switch.isChecked(),
+ )
+ )
# 距上次抽取时间是否纳入计算
self.fair_draw_time_switch = SwitchButton()
- self.fair_draw_time_switch.setOffText(get_content_switchbutton_name_async("advanced_settings", "fair_draw_time", "disable"))
- self.fair_draw_time_switch.setOnText(get_content_switchbutton_name_async("advanced_settings", "fair_draw_time", "enable"))
- self.fair_draw_time_switch.setChecked(readme_settings_async("advanced_settings", "fair_draw_time"))
- self.fair_draw_time_switch.checkedChanged.connect(lambda: update_settings("advanced_settings", "fair_draw_time", self.fair_draw_time_switch.isChecked()))
+ self.fair_draw_time_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "advanced_settings", "fair_draw_time", "disable"
+ )
+ )
+ self.fair_draw_time_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "advanced_settings", "fair_draw_time", "enable"
+ )
+ )
+ self.fair_draw_time_switch.setChecked(
+ readme_settings_async("advanced_settings", "fair_draw_time")
+ )
+ self.fair_draw_time_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "advanced_settings",
+ "fair_draw_time",
+ self.fair_draw_time_switch.isChecked(),
+ )
+ )
# 设置基础权重
self.base_weight_spinbox = DoubleSpinBox()
self.base_weight_spinbox.setFixedWidth(WIDTH_SPINBOX)
self.base_weight_spinbox.setRange(0.01, 1000.00)
- self.base_weight_spinbox.setValue(readme_settings_async("advanced_settings", "base_weight"))
- self.base_weight_spinbox.valueChanged.connect(lambda: update_settings("advanced_settings", "base_weight", self.base_weight_spinbox.value()))
+ self.base_weight_spinbox.setValue(
+ readme_settings_async("advanced_settings", "base_weight")
+ )
+ self.base_weight_spinbox.valueChanged.connect(
+ lambda: update_settings(
+ "advanced_settings", "base_weight", self.base_weight_spinbox.value()
+ )
+ )
# 设置权重范围最小值
self.min_weight_spinbox = DoubleSpinBox()
self.min_weight_spinbox.setFixedWidth(WIDTH_SPINBOX)
self.min_weight_spinbox.setRange(0.01, 1000.00)
- self.min_weight_spinbox.setValue(readme_settings_async("advanced_settings", "min_weight"))
- self.min_weight_spinbox.valueChanged.connect(lambda: update_settings("advanced_settings", "min_weight", self.min_weight_spinbox.value()))
+ self.min_weight_spinbox.setValue(
+ readme_settings_async("advanced_settings", "min_weight")
+ )
+ self.min_weight_spinbox.valueChanged.connect(
+ lambda: update_settings(
+ "advanced_settings", "min_weight", self.min_weight_spinbox.value()
+ )
+ )
# 设置权重范围最大值
self.max_weight_spinbox = DoubleSpinBox()
self.max_weight_spinbox.setFixedWidth(WIDTH_SPINBOX)
self.max_weight_spinbox.setRange(0.01, 1000.00)
- self.max_weight_spinbox.setValue(readme_settings_async("advanced_settings", "max_weight"))
- self.max_weight_spinbox.valueChanged.connect(lambda: update_settings("advanced_settings", "max_weight", self.max_weight_spinbox.value()))
+ self.max_weight_spinbox.setValue(
+ readme_settings_async("advanced_settings", "max_weight")
+ )
+ self.max_weight_spinbox.valueChanged.connect(
+ lambda: update_settings(
+ "advanced_settings", "max_weight", self.max_weight_spinbox.value()
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_document_bullet_list_clock_20_filled"),
- get_content_name_async("advanced_settings", "fair_draw"), get_content_description_async("advanced_settings", "fair_draw"), self.fair_draw_switch)
- self.addGroup(get_theme_icon("ic_fluent_document_bullet_list_clock_20_filled"),
- get_content_name_async("advanced_settings", "fair_draw_group"), get_content_description_async("advanced_settings", "fair_draw_group"), self.fair_draw_group_switch)
- self.addGroup(get_theme_icon("ic_fluent_document_bullet_list_clock_20_filled"),
- get_content_name_async("advanced_settings", "fair_draw_gender"), get_content_description_async("advanced_settings", "fair_draw_gender"), self.fair_draw_gender_switch)
- self.addGroup(get_theme_icon("ic_fluent_document_bullet_list_clock_20_filled"),
- get_content_name_async("advanced_settings", "fair_draw_time"), get_content_description_async("advanced_settings", "fair_draw_time"), self.fair_draw_time_switch)
- self.addGroup(get_theme_icon("ic_fluent_document_bullet_list_clock_20_filled"),
- get_content_name_async("advanced_settings", "base_weight"), get_content_description_async("advanced_settings", "base_weight"), self.base_weight_spinbox)
- self.addGroup(get_theme_icon("ic_fluent_document_bullet_list_clock_20_filled"),
- get_content_name_async("advanced_settings", "min_weight"), get_content_description_async("advanced_settings", "min_weight"), self.min_weight_spinbox)
- self.addGroup(get_theme_icon("ic_fluent_document_bullet_list_clock_20_filled"),
- get_content_name_async("advanced_settings", "max_weight"), get_content_description_async("advanced_settings", "max_weight"), self.max_weight_spinbox)
+ self.addGroup(
+ get_theme_icon("ic_fluent_document_bullet_list_clock_20_filled"),
+ get_content_name_async("advanced_settings", "fair_draw"),
+ get_content_description_async("advanced_settings", "fair_draw"),
+ self.fair_draw_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_document_bullet_list_clock_20_filled"),
+ get_content_name_async("advanced_settings", "fair_draw_group"),
+ get_content_description_async("advanced_settings", "fair_draw_group"),
+ self.fair_draw_group_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_document_bullet_list_clock_20_filled"),
+ get_content_name_async("advanced_settings", "fair_draw_gender"),
+ get_content_description_async("advanced_settings", "fair_draw_gender"),
+ self.fair_draw_gender_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_document_bullet_list_clock_20_filled"),
+ get_content_name_async("advanced_settings", "fair_draw_time"),
+ get_content_description_async("advanced_settings", "fair_draw_time"),
+ self.fair_draw_time_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_document_bullet_list_clock_20_filled"),
+ get_content_name_async("advanced_settings", "base_weight"),
+ get_content_description_async("advanced_settings", "base_weight"),
+ self.base_weight_spinbox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_document_bullet_list_clock_20_filled"),
+ get_content_name_async("advanced_settings", "min_weight"),
+ get_content_description_async("advanced_settings", "min_weight"),
+ self.min_weight_spinbox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_document_bullet_list_clock_20_filled"),
+ get_content_name_async("advanced_settings", "max_weight"),
+ get_content_description_async("advanced_settings", "max_weight"),
+ self.max_weight_spinbox,
+ )
diff --git a/app/view/settings/more_settings/debug.py b/app/view/settings/more_settings/debug.py
index 1aed02ed..7b49913b 100644
--- a/app/view/settings/more_settings/debug.py
+++ b/app/view/settings/more_settings/debug.py
@@ -1,16 +1,11 @@
# ==================================================
# 导入库
# ==================================================
-import json
-import os
-import sys
-import subprocess
-from loguru import logger
-from PyQt6.QtWidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from PySide6.QtNetwork import *
from qfluentwidgets import *
from app.tools.variable import *
@@ -20,6 +15,7 @@
from app.tools.settings_access import *
from app.Language.obtain_language import *
+
# ==================================================
# 调试设置
# ==================================================
@@ -34,12 +30,16 @@ def __init__(self, parent=None):
self.all_monitor_combo_box.addItems(self.get_monitor_list())
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_panel_separate_window_20_filled"),
- "显示器列表", "显示所有显示器列表(DEBUG)", self.all_monitor_combo_box)
+ self.addGroup(
+ get_theme_icon("ic_fluent_panel_separate_window_20_filled"),
+ "显示器列表",
+ "显示所有显示器列表(DEBUG)",
+ self.all_monitor_combo_box,
+ )
def get_monitor_list(self):
# 获取所有显示器名称
monitor_list = []
for screen in QApplication.instance().screens():
monitor_list.append(screen.name())
- return monitor_list
\ No newline at end of file
+ return monitor_list
diff --git a/app/view/settings/more_settings/experimental_features.py b/app/view/settings/more_settings/experimental_features.py
index a19bdd94..b826c49b 100644
--- a/app/view/settings/more_settings/experimental_features.py
+++ b/app/view/settings/more_settings/experimental_features.py
@@ -1,16 +1,11 @@
# ==================================================
# 导入库
# ==================================================
-import json
-import os
-import sys
-import subprocess
-from loguru import logger
-from PyQt6.QtWidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from PySide6.QtNetwork import *
from qfluentwidgets import *
from app.tools.variable import *
@@ -19,4 +14,3 @@
from app.tools.settings_default import *
from app.tools.settings_access import *
from app.Language.obtain_language import *
-
diff --git a/app/view/settings/notification_settings/__init__.py b/app/view/settings/notification_settings/__init__.py
new file mode 100644
index 00000000..40d5fa2d
--- /dev/null
+++ b/app/view/settings/notification_settings/__init__.py
@@ -0,0 +1 @@
+"""Notification settings pages."""
diff --git a/app/view/settings/notification_settings/custom_draw_notification_settings.py b/app/view/settings/notification_settings/custom_draw_notification_settings.py
index 0aa09a90..6f1cdaf0 100644
--- a/app/view/settings/notification_settings/custom_draw_notification_settings.py
+++ b/app/view/settings/notification_settings/custom_draw_notification_settings.py
@@ -1,16 +1,11 @@
# ==================================================
# 导入库
# ==================================================
-import json
-import os
-import sys
-import subprocess
-
-from loguru import logger
-from PyQt6.QtWidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
+
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from PySide6.QtNetwork import *
from qfluentwidgets import *
from app.tools.variable import *
@@ -20,6 +15,7 @@
from app.tools.settings_access import *
from app.Language.obtain_language import *
+
# ==================================================
# 自定义绘制通知设置类
# ==================================================
@@ -38,101 +34,281 @@ def __init__(self, parent=None):
# 添加窗口模式设置组件
self.window_mode_widget = window_mode(self)
self.vBoxLayout.addWidget(self.window_mode_widget)
-
+
# 添加浮窗模式设置组件
self.floating_window_widget = floating_window_settings(self)
- self.vBoxLayout.addWidget(self.floating_window_widget)
+ self.vBoxLayout.addWidget(self.floating_window_widget)
+
class basic_settings(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("custom_draw_notification_settings", "basic_settings"))
+ self.setTitle(
+ get_content_name_async(
+ "custom_draw_notification_settings", "basic_settings"
+ )
+ )
self.setBorderRadius(8)
# 是否需要调用通知服务
self.call_notification_service_switch = SwitchButton()
- self.call_notification_service_switch.setOffText(get_content_switchbutton_name_async("custom_draw_notification_settings", "call_notification_service", "disable"))
- self.call_notification_service_switch.setOnText(get_content_switchbutton_name_async("custom_draw_notification_settings", "call_notification_service", "enable"))
- self.call_notification_service_switch.setChecked(readme_settings_async("custom_draw_notification_settings", "call_notification_service"))
- self.call_notification_service_switch.checkedChanged.connect(lambda: update_settings("custom_draw_notification_settings", "call_notification_service", self.call_notification_service_switch.isChecked()))
+ self.call_notification_service_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "custom_draw_notification_settings",
+ "call_notification_service",
+ "disable",
+ )
+ )
+ self.call_notification_service_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "custom_draw_notification_settings",
+ "call_notification_service",
+ "enable",
+ )
+ )
+ self.call_notification_service_switch.setChecked(
+ readme_settings_async(
+ "custom_draw_notification_settings", "call_notification_service"
+ )
+ )
+ self.call_notification_service_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "custom_draw_notification_settings",
+ "call_notification_service",
+ self.call_notification_service_switch.isChecked(),
+ )
+ )
# 选择通知模式下拉框
self.notification_mode_combo_box = ComboBox()
- self.notification_mode_combo_box.addItems(get_content_combo_name_async("custom_draw_notification_settings", "notification_mode"))
- self.notification_mode_combo_box.setCurrentText(get_content_name_async("custom_draw_notification_settings", "notification_mode"))
- self.notification_mode_combo_box.currentIndexChanged.connect(lambda: update_settings("custom_draw_notification_settings", "notification_mode", self.notification_mode_combo_box.currentIndex()))
+ self.notification_mode_combo_box.addItems(
+ get_content_combo_name_async(
+ "custom_draw_notification_settings", "notification_mode"
+ )
+ )
+ self.notification_mode_combo_box.setCurrentText(
+ get_content_name_async(
+ "custom_draw_notification_settings", "notification_mode"
+ )
+ )
+ self.notification_mode_combo_box.currentIndexChanged.connect(
+ lambda: update_settings(
+ "custom_draw_notification_settings",
+ "notification_mode",
+ self.notification_mode_combo_box.currentIndex(),
+ )
+ )
# 是否开启动画开关
self.animation_switch = SwitchButton()
- self.animation_switch.setOffText(get_content_switchbutton_name_async("custom_draw_notification_settings", "animation", "disable"))
- self.animation_switch.setOnText(get_content_switchbutton_name_async("custom_draw_notification_settings", "animation", "enable"))
- self.animation_switch.setChecked(readme_settings_async("custom_draw_notification_settings", "animation"))
- self.animation_switch.checkedChanged.connect(lambda: update_settings("custom_draw_notification_settings", "animation", self.animation_switch.isChecked()))
+ self.animation_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "custom_draw_notification_settings", "animation", "disable"
+ )
+ )
+ self.animation_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "custom_draw_notification_settings", "animation", "enable"
+ )
+ )
+ self.animation_switch.setChecked(
+ readme_settings_async("custom_draw_notification_settings", "animation")
+ )
+ self.animation_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "custom_draw_notification_settings",
+ "animation",
+ self.animation_switch.isChecked(),
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_comment_20_filled"),
- get_content_name_async("custom_draw_notification_settings", "call_notification_service"), get_content_description_async("custom_draw_notification_settings", "call_notification_service"), self.call_notification_service_switch)
- self.addGroup(get_theme_icon("ic_fluent_comment_20_filled"),
- get_content_name_async("custom_draw_notification_settings", "notification_mode"), get_content_description_async("custom_draw_notification_settings", "notification_mode"), self.notification_mode_combo_box)
- self.addGroup(get_theme_icon("ic_fluent_sanitize_20_filled"),
- get_content_name_async("custom_draw_notification_settings", "animation"), get_content_description_async("custom_draw_notification_settings", "animation"), self.animation_switch)
+ self.addGroup(
+ get_theme_icon("ic_fluent_comment_20_filled"),
+ get_content_name_async(
+ "custom_draw_notification_settings", "call_notification_service"
+ ),
+ get_content_description_async(
+ "custom_draw_notification_settings", "call_notification_service"
+ ),
+ self.call_notification_service_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_comment_20_filled"),
+ get_content_name_async(
+ "custom_draw_notification_settings", "notification_mode"
+ ),
+ get_content_description_async(
+ "custom_draw_notification_settings", "notification_mode"
+ ),
+ self.notification_mode_combo_box,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_sanitize_20_filled"),
+ get_content_name_async("custom_draw_notification_settings", "animation"),
+ get_content_description_async(
+ "custom_draw_notification_settings", "animation"
+ ),
+ self.animation_switch,
+ )
+
class window_mode(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("custom_draw_notification_settings", "window_mode"))
+ self.setTitle(
+ get_content_name_async("custom_draw_notification_settings", "window_mode")
+ )
self.setBorderRadius(8)
-
+
# 设置窗口的显示位置下拉框
self.window_position_combo_box = ComboBox()
- self.window_position_combo_box.addItems(get_content_combo_name_async("custom_draw_notification_settings", "window_position"))
- self.window_position_combo_box.setCurrentIndex(readme_settings_async("custom_draw_notification_settings", "window_position"))
- self.window_position_combo_box.currentIndexChanged.connect(lambda: update_settings("custom_draw_notification_settings", "window_position", self.window_position_combo_box.currentIndex()))
+ self.window_position_combo_box.addItems(
+ get_content_combo_name_async(
+ "custom_draw_notification_settings", "window_position"
+ )
+ )
+ self.window_position_combo_box.setCurrentIndex(
+ readme_settings_async(
+ "custom_draw_notification_settings", "window_position"
+ )
+ )
+ self.window_position_combo_box.currentIndexChanged.connect(
+ lambda: update_settings(
+ "custom_draw_notification_settings",
+ "window_position",
+ self.window_position_combo_box.currentIndex(),
+ )
+ )
# 水平偏移值
self.horizontal_offset_spin_spinbox = SpinBox()
self.horizontal_offset_spin_spinbox.setFixedWidth(WIDTH_SPINBOX)
self.horizontal_offset_spin_spinbox.setRange(-25600, 25600)
self.horizontal_offset_spin_spinbox.setSuffix("px")
- self.horizontal_offset_spin_spinbox.setValue(readme_settings_async("custom_draw_notification_settings", "horizontal_offset"))
- self.horizontal_offset_spin_spinbox.valueChanged.connect(lambda: update_settings("custom_draw_notification_settings", "horizontal_offset", self.horizontal_offset_spin_spinbox.value()))
+ self.horizontal_offset_spin_spinbox.setValue(
+ readme_settings_async(
+ "custom_draw_notification_settings", "horizontal_offset"
+ )
+ )
+ self.horizontal_offset_spin_spinbox.valueChanged.connect(
+ lambda: update_settings(
+ "custom_draw_notification_settings",
+ "horizontal_offset",
+ self.horizontal_offset_spin_spinbox.value(),
+ )
+ )
# 垂直偏移值
self.vertical_offset_spin_spinbox = SpinBox()
self.vertical_offset_spin_spinbox.setFixedWidth(WIDTH_SPINBOX)
self.vertical_offset_spin_spinbox.setRange(-25600, 25600)
self.vertical_offset_spin_spinbox.setSuffix("px")
- self.vertical_offset_spin_spinbox.setValue(readme_settings_async("custom_draw_notification_settings", "vertical_offset"))
- self.vertical_offset_spin_spinbox.valueChanged.connect(lambda: update_settings("custom_draw_notification_settings", "vertical_offset", self.vertical_offset_spin_spinbox.value()))
+ self.vertical_offset_spin_spinbox.setValue(
+ readme_settings_async(
+ "custom_draw_notification_settings", "vertical_offset"
+ )
+ )
+ self.vertical_offset_spin_spinbox.valueChanged.connect(
+ lambda: update_settings(
+ "custom_draw_notification_settings",
+ "vertical_offset",
+ self.vertical_offset_spin_spinbox.value(),
+ )
+ )
# 窗口透明度
self.window_transparency_spin_spinbox = SpinBox()
self.window_transparency_spin_spinbox.setFixedWidth(WIDTH_SPINBOX)
self.window_transparency_spin_spinbox.setRange(0, 100)
self.window_transparency_spin_spinbox.setSuffix("%")
- self.window_transparency_spin_spinbox.setValue(readme_settings_async("custom_draw_notification_settings", "transparency") * 100)
- self.window_transparency_spin_spinbox.valueChanged.connect(lambda: update_settings("custom_draw_notification_settings", "transparency", self.window_transparency_spin_spinbox.value() / 100))
+ self.window_transparency_spin_spinbox.setValue(
+ readme_settings_async("custom_draw_notification_settings", "transparency")
+ * 100
+ )
+ self.window_transparency_spin_spinbox.valueChanged.connect(
+ lambda: update_settings(
+ "custom_draw_notification_settings",
+ "transparency",
+ self.window_transparency_spin_spinbox.value() / 100,
+ )
+ )
# 选择启用的显示器下拉框
self.enabled_monitor_combo_box = ComboBox()
self.enabled_monitor_combo_box.addItems(self.get_monitor_list())
- if readme_settings_async("custom_draw_notification_settings", "enabled_monitor") == "OFF":
+ if (
+ readme_settings_async(
+ "custom_draw_notification_settings", "enabled_monitor"
+ )
+ == "OFF"
+ ):
self.enabled_monitor_combo_box.setCurrentText(self.get_monitor_list()[0])
- update_settings("custom_draw_notification_settings", "enabled_monitor", self.enabled_monitor_combo_box.currentText())
- self.enabled_monitor_combo_box.setCurrentText(readme_settings_async("custom_draw_notification_settings", "enabled_monitor"))
- self.enabled_monitor_combo_box.currentTextChanged.connect(lambda: self.on_first_monitor_changed(self.enabled_monitor_combo_box.currentText()))
+ update_settings(
+ "custom_draw_notification_settings",
+ "enabled_monitor",
+ self.enabled_monitor_combo_box.currentText(),
+ )
+ self.enabled_monitor_combo_box.setCurrentText(
+ readme_settings_async(
+ "custom_draw_notification_settings", "enabled_monitor"
+ )
+ )
+ self.enabled_monitor_combo_box.currentTextChanged.connect(
+ lambda: self.on_first_monitor_changed(
+ self.enabled_monitor_combo_box.currentText()
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_window_text_20_filled"),
- get_content_name_async("custom_draw_notification_settings", "enabled_monitor"), get_content_description_async("custom_draw_notification_settings", "enabled_monitor"), self.enabled_monitor_combo_box)
- self.addGroup(get_theme_icon("ic_fluent_position_to_back_20_filled"),
- get_content_name_async("custom_draw_notification_settings", "window_position"), get_content_description_async("custom_draw_notification_settings", "window_position"), self.window_position_combo_box)
- self.addGroup(get_theme_icon("ic_fluent_align_stretch_horizontal_20_filled"),
- get_content_name_async("custom_draw_notification_settings", "horizontal_offset"), get_content_description_async("custom_draw_notification_settings", "horizontal_offset"), self.horizontal_offset_spin_spinbox)
- self.addGroup(get_theme_icon("ic_fluent_align_stretch_vertical_20_filled"),
- get_content_name_async("custom_draw_notification_settings", "vertical_offset"), get_content_description_async("custom_draw_notification_settings", "vertical_offset"), self.vertical_offset_spin_spinbox)
- self.addGroup(get_theme_icon("ic_fluent_transparency_square_20_filled"),
- get_content_name_async("custom_draw_notification_settings", "transparency"), get_content_description_async("custom_draw_notification_settings", "transparency"), self.window_transparency_spin_spinbox)
+ self.addGroup(
+ get_theme_icon("ic_fluent_window_text_20_filled"),
+ get_content_name_async(
+ "custom_draw_notification_settings", "enabled_monitor"
+ ),
+ get_content_description_async(
+ "custom_draw_notification_settings", "enabled_monitor"
+ ),
+ self.enabled_monitor_combo_box,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_position_to_back_20_filled"),
+ get_content_name_async(
+ "custom_draw_notification_settings", "window_position"
+ ),
+ get_content_description_async(
+ "custom_draw_notification_settings", "window_position"
+ ),
+ self.window_position_combo_box,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_align_stretch_horizontal_20_filled"),
+ get_content_name_async(
+ "custom_draw_notification_settings", "horizontal_offset"
+ ),
+ get_content_description_async(
+ "custom_draw_notification_settings", "horizontal_offset"
+ ),
+ self.horizontal_offset_spin_spinbox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_align_stretch_vertical_20_filled"),
+ get_content_name_async(
+ "custom_draw_notification_settings", "vertical_offset"
+ ),
+ get_content_description_async(
+ "custom_draw_notification_settings", "vertical_offset"
+ ),
+ self.vertical_offset_spin_spinbox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_transparency_square_20_filled"),
+ get_content_name_async("custom_draw_notification_settings", "transparency"),
+ get_content_description_async(
+ "custom_draw_notification_settings", "transparency"
+ ),
+ self.window_transparency_spin_spinbox,
+ )
# 获取显示器列表
def get_monitor_list(self):
@@ -141,62 +317,169 @@ def get_monitor_list(self):
monitor_list.append(screen.name())
return monitor_list
+
class floating_window_settings(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("custom_draw_notification_settings", "floating_window_mode"))
+ self.setTitle(
+ get_content_name_async(
+ "custom_draw_notification_settings", "floating_window_mode"
+ )
+ )
self.setBorderRadius(8)
# 窗口位置
self.window_position_combo_box = ComboBox()
- self.window_position_combo_box.addItems(get_content_combo_name_async("custom_draw_notification_settings", "floating_window_position"))
- self.window_position_combo_box.setCurrentIndex(readme_settings_async("custom_draw_notification_settings", "floating_window_position"))
- self.window_position_combo_box.currentTextChanged.connect(lambda: update_settings("custom_draw_notification_settings", "floating_window_position", self.window_position_combo_box.currentIndex()))
-
+ self.window_position_combo_box.addItems(
+ get_content_combo_name_async(
+ "custom_draw_notification_settings", "floating_window_position"
+ )
+ )
+ self.window_position_combo_box.setCurrentIndex(
+ readme_settings_async(
+ "custom_draw_notification_settings", "floating_window_position"
+ )
+ )
+ self.window_position_combo_box.currentTextChanged.connect(
+ lambda: update_settings(
+ "custom_draw_notification_settings",
+ "floating_window_position",
+ self.window_position_combo_box.currentIndex(),
+ )
+ )
+
# 水平偏移值
self.horizontal_offset_spin_spinbox = SpinBox()
self.horizontal_offset_spin_spinbox.setFixedWidth(WIDTH_SPINBOX)
self.horizontal_offset_spin_spinbox.setRange(-25600, 25600)
self.horizontal_offset_spin_spinbox.setSuffix("px")
- self.horizontal_offset_spin_spinbox.setValue(readme_settings_async("custom_draw_notification_settings", "floating_window_horizontal_offset"))
- self.horizontal_offset_spin_spinbox.valueChanged.connect(lambda: update_settings("custom_draw_notification_settings", "floating_window_horizontal_offset", self.horizontal_offset_spin_spinbox.value()))
+ self.horizontal_offset_spin_spinbox.setValue(
+ readme_settings_async(
+ "custom_draw_notification_settings", "floating_window_horizontal_offset"
+ )
+ )
+ self.horizontal_offset_spin_spinbox.valueChanged.connect(
+ lambda: update_settings(
+ "custom_draw_notification_settings",
+ "floating_window_horizontal_offset",
+ self.horizontal_offset_spin_spinbox.value(),
+ )
+ )
# 垂直偏移值
self.vertical_offset_spin_spinbox = SpinBox()
self.vertical_offset_spin_spinbox.setFixedWidth(WIDTH_SPINBOX)
self.vertical_offset_spin_spinbox.setRange(-25600, 25600)
self.vertical_offset_spin_spinbox.setSuffix("px")
- self.vertical_offset_spin_spinbox.setValue(readme_settings_async("custom_draw_notification_settings", "floating_window_vertical_offset"))
- self.vertical_offset_spin_spinbox.valueChanged.connect(lambda: update_settings("custom_draw_notification_settings", "floating_window_vertical_offset", self.vertical_offset_spin_spinbox.value()))
+ self.vertical_offset_spin_spinbox.setValue(
+ readme_settings_async(
+ "custom_draw_notification_settings", "floating_window_vertical_offset"
+ )
+ )
+ self.vertical_offset_spin_spinbox.valueChanged.connect(
+ lambda: update_settings(
+ "custom_draw_notification_settings",
+ "floating_window_vertical_offset",
+ self.vertical_offset_spin_spinbox.value(),
+ )
+ )
# 窗口透明度
self.window_transparency_spin_spinbox = SpinBox()
self.window_transparency_spin_spinbox.setFixedWidth(WIDTH_SPINBOX)
self.window_transparency_spin_spinbox.setRange(0, 100)
self.window_transparency_spin_spinbox.setSuffix("%")
- self.window_transparency_spin_spinbox.setValue(readme_settings_async("custom_draw_notification_settings", "floating_window_transparency") * 100)
- self.window_transparency_spin_spinbox.valueChanged.connect(lambda: update_settings("custom_draw_notification_settings", "floating_window_transparency", self.window_transparency_spin_spinbox.value() / 100))
+ self.window_transparency_spin_spinbox.setValue(
+ readme_settings_async(
+ "custom_draw_notification_settings", "floating_window_transparency"
+ )
+ * 100
+ )
+ self.window_transparency_spin_spinbox.valueChanged.connect(
+ lambda: update_settings(
+ "custom_draw_notification_settings",
+ "floating_window_transparency",
+ self.window_transparency_spin_spinbox.value() / 100,
+ )
+ )
# 选择启用的显示器下拉框
self.enabled_monitor_combo_box = ComboBox()
self.enabled_monitor_combo_box.addItems(self.get_monitor_list())
- if readme_settings_async("custom_draw_notification_settings", "floating_window_enabled_monitor") == "OFF":
+ if (
+ readme_settings_async(
+ "custom_draw_notification_settings", "floating_window_enabled_monitor"
+ )
+ == "OFF"
+ ):
self.enabled_monitor_combo_box.setCurrentText(self.get_monitor_list()[0])
- update_settings("custom_draw_notification_settings", "floating_window_enabled_monitor", self.enabled_monitor_combo_box.currentText())
- self.enabled_monitor_combo_box.setCurrentText(readme_settings_async("custom_draw_notification_settings", "floating_window_enabled_monitor"))
- self.enabled_monitor_combo_box.currentTextChanged.connect(lambda: self.on_floating_first_monitor_changed(self.enabled_monitor_combo_box.currentText()))
-
+ update_settings(
+ "custom_draw_notification_settings",
+ "floating_window_enabled_monitor",
+ self.enabled_monitor_combo_box.currentText(),
+ )
+ self.enabled_monitor_combo_box.setCurrentText(
+ readme_settings_async(
+ "custom_draw_notification_settings", "floating_window_enabled_monitor"
+ )
+ )
+ self.enabled_monitor_combo_box.currentTextChanged.connect(
+ lambda: self.on_floating_first_monitor_changed(
+ self.enabled_monitor_combo_box.currentText()
+ )
+ )
+
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_window_text_20_filled"),
- get_content_name_async("custom_draw_notification_settings", "floating_window_enabled_monitor"), get_content_description_async("custom_draw_notification_settings", "floating_window_enabled_monitor"), self.enabled_monitor_combo_box)
- self.addGroup(get_theme_icon("ic_fluent_position_to_back_20_filled"),
- get_content_name_async("custom_draw_notification_settings", "floating_window_position"), get_content_description_async("custom_draw_notification_settings", "floating_window_position"), self.window_position_combo_box)
- self.addGroup(get_theme_icon("ic_fluent_align_stretch_horizontal_20_filled"),
- get_content_name_async("custom_draw_notification_settings", "floating_window_horizontal_offset"), get_content_description_async("custom_draw_notification_settings", "floating_window_horizontal_offset"), self.horizontal_offset_spin_spinbox)
- self.addGroup(get_theme_icon("ic_fluent_align_stretch_vertical_20_filled"),
- get_content_name_async("custom_draw_notification_settings", "floating_window_vertical_offset"), get_content_description_async("custom_draw_notification_settings", "floating_window_vertical_offset"), self.vertical_offset_spin_spinbox)
- self.addGroup(get_theme_icon("ic_fluent_transparency_square_20_filled"),
- get_content_name_async("custom_draw_notification_settings", "floating_window_transparency"), get_content_description_async("custom_draw_notification_settings", "floating_window_transparency"), self.window_transparency_spin_spinbox)
+ self.addGroup(
+ get_theme_icon("ic_fluent_window_text_20_filled"),
+ get_content_name_async(
+ "custom_draw_notification_settings", "floating_window_enabled_monitor"
+ ),
+ get_content_description_async(
+ "custom_draw_notification_settings", "floating_window_enabled_monitor"
+ ),
+ self.enabled_monitor_combo_box,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_position_to_back_20_filled"),
+ get_content_name_async(
+ "custom_draw_notification_settings", "floating_window_position"
+ ),
+ get_content_description_async(
+ "custom_draw_notification_settings", "floating_window_position"
+ ),
+ self.window_position_combo_box,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_align_stretch_horizontal_20_filled"),
+ get_content_name_async(
+ "custom_draw_notification_settings", "floating_window_horizontal_offset"
+ ),
+ get_content_description_async(
+ "custom_draw_notification_settings", "floating_window_horizontal_offset"
+ ),
+ self.horizontal_offset_spin_spinbox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_align_stretch_vertical_20_filled"),
+ get_content_name_async(
+ "custom_draw_notification_settings", "floating_window_vertical_offset"
+ ),
+ get_content_description_async(
+ "custom_draw_notification_settings", "floating_window_vertical_offset"
+ ),
+ self.vertical_offset_spin_spinbox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_transparency_square_20_filled"),
+ get_content_name_async(
+ "custom_draw_notification_settings", "floating_window_transparency"
+ ),
+ get_content_description_async(
+ "custom_draw_notification_settings", "floating_window_transparency"
+ ),
+ self.window_transparency_spin_spinbox,
+ )
# 获取显示器列表
def get_monitor_list(self):
diff --git a/app/view/settings/notification_settings/instant_draw_notification_settings.py b/app/view/settings/notification_settings/instant_draw_notification_settings.py
index 2d7f6896..5bcd886f 100644
--- a/app/view/settings/notification_settings/instant_draw_notification_settings.py
+++ b/app/view/settings/notification_settings/instant_draw_notification_settings.py
@@ -1,16 +1,11 @@
# ==================================================
# 导入库
# ==================================================
-import json
-import os
-import sys
-import subprocess
-
-from loguru import logger
-from PyQt6.QtWidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
+
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from PySide6.QtNetwork import *
from qfluentwidgets import *
from app.tools.variable import *
@@ -20,6 +15,7 @@
from app.tools.settings_access import *
from app.Language.obtain_language import *
+
# ==================================================
# 即抽通知设置类
# ==================================================
@@ -38,92 +34,244 @@ def __init__(self, parent=None):
# 添加窗口模式设置组件
self.window_mode_widget = window_mode(self)
self.vBoxLayout.addWidget(self.window_mode_widget)
-
+
# 添加浮窗模式设置组件
self.floating_window_widget = floating_window_settings(self)
- self.vBoxLayout.addWidget(self.floating_window_widget)
+ self.vBoxLayout.addWidget(self.floating_window_widget)
+
class basic_settings(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("instant_draw_notification_settings", "basic_settings"))
+ self.setTitle(
+ get_content_name_async(
+ "instant_draw_notification_settings", "basic_settings"
+ )
+ )
self.setBorderRadius(8)
-
+
# 选择通知模式下拉框
self.notification_mode_combo_box = ComboBox()
- self.notification_mode_combo_box.addItems(get_content_combo_name_async("instant_draw_notification_settings", "notification_mode"))
- self.notification_mode_combo_box.setCurrentText(get_content_name_async("instant_draw_notification_settings", "notification_mode"))
- self.notification_mode_combo_box.currentIndexChanged.connect(lambda: update_settings("instant_draw_notification_settings", "notification_mode", self.notification_mode_combo_box.currentIndex()))
+ self.notification_mode_combo_box.addItems(
+ get_content_combo_name_async(
+ "instant_draw_notification_settings", "notification_mode"
+ )
+ )
+ self.notification_mode_combo_box.setCurrentText(
+ get_content_name_async(
+ "instant_draw_notification_settings", "notification_mode"
+ )
+ )
+ self.notification_mode_combo_box.currentIndexChanged.connect(
+ lambda: update_settings(
+ "instant_draw_notification_settings",
+ "notification_mode",
+ self.notification_mode_combo_box.currentIndex(),
+ )
+ )
# 是否开启动画开关
self.animation_switch = SwitchButton()
- self.animation_switch.setOffText(get_content_switchbutton_name_async("instant_draw_notification_settings", "animation", "disable"))
- self.animation_switch.setOnText(get_content_switchbutton_name_async("instant_draw_notification_settings", "animation", "enable"))
- self.animation_switch.setChecked(readme_settings_async("instant_draw_notification_settings", "animation"))
- self.animation_switch.checkedChanged.connect(lambda: update_settings("instant_draw_notification_settings", "animation", self.animation_switch.isChecked()))
+ self.animation_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "instant_draw_notification_settings", "animation", "disable"
+ )
+ )
+ self.animation_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "instant_draw_notification_settings", "animation", "enable"
+ )
+ )
+ self.animation_switch.setChecked(
+ readme_settings_async("instant_draw_notification_settings", "animation")
+ )
+ self.animation_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "instant_draw_notification_settings",
+ "animation",
+ self.animation_switch.isChecked(),
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_comment_20_filled"),
- get_content_name_async("instant_draw_notification_settings", "notification_mode"), get_content_description_async("instant_draw_notification_settings", "notification_mode"), self.notification_mode_combo_box)
- self.addGroup(get_theme_icon("ic_fluent_sanitize_20_filled"),
- get_content_name_async("instant_draw_notification_settings", "animation"), get_content_description_async("instant_draw_notification_settings", "animation"), self.animation_switch)
+ self.addGroup(
+ get_theme_icon("ic_fluent_comment_20_filled"),
+ get_content_name_async(
+ "instant_draw_notification_settings", "notification_mode"
+ ),
+ get_content_description_async(
+ "instant_draw_notification_settings", "notification_mode"
+ ),
+ self.notification_mode_combo_box,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_sanitize_20_filled"),
+ get_content_name_async("instant_draw_notification_settings", "animation"),
+ get_content_description_async(
+ "instant_draw_notification_settings", "animation"
+ ),
+ self.animation_switch,
+ )
+
class window_mode(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("instant_draw_notification_settings", "window_mode"))
+ self.setTitle(
+ get_content_name_async("instant_draw_notification_settings", "window_mode")
+ )
self.setBorderRadius(8)
-
+
# 设置窗口的显示位置下拉框
self.window_position_combo_box = ComboBox()
- self.window_position_combo_box.addItems(get_content_combo_name_async("instant_draw_notification_settings", "window_position"))
- self.window_position_combo_box.setCurrentIndex(readme_settings_async("instant_draw_notification_settings", "window_position"))
- self.window_position_combo_box.currentIndexChanged.connect(lambda: update_settings("instant_draw_notification_settings", "window_position", self.window_position_combo_box.currentIndex()))
+ self.window_position_combo_box.addItems(
+ get_content_combo_name_async(
+ "instant_draw_notification_settings", "window_position"
+ )
+ )
+ self.window_position_combo_box.setCurrentIndex(
+ readme_settings_async(
+ "instant_draw_notification_settings", "window_position"
+ )
+ )
+ self.window_position_combo_box.currentIndexChanged.connect(
+ lambda: update_settings(
+ "instant_draw_notification_settings",
+ "window_position",
+ self.window_position_combo_box.currentIndex(),
+ )
+ )
# 水平偏移值
self.horizontal_offset_spin_spinbox = SpinBox()
self.horizontal_offset_spin_spinbox.setFixedWidth(WIDTH_SPINBOX)
self.horizontal_offset_spin_spinbox.setRange(-25600, 25600)
self.horizontal_offset_spin_spinbox.setSuffix("px")
- self.horizontal_offset_spin_spinbox.setValue(readme_settings_async("instant_draw_notification_settings", "horizontal_offset"))
- self.horizontal_offset_spin_spinbox.valueChanged.connect(lambda: update_settings("instant_draw_notification_settings", "horizontal_offset", self.horizontal_offset_spin_spinbox.value()))
+ self.horizontal_offset_spin_spinbox.setValue(
+ readme_settings_async(
+ "instant_draw_notification_settings", "horizontal_offset"
+ )
+ )
+ self.horizontal_offset_spin_spinbox.valueChanged.connect(
+ lambda: update_settings(
+ "instant_draw_notification_settings",
+ "horizontal_offset",
+ self.horizontal_offset_spin_spinbox.value(),
+ )
+ )
# 垂直偏移值
self.vertical_offset_spin_spinbox = SpinBox()
self.vertical_offset_spin_spinbox.setFixedWidth(WIDTH_SPINBOX)
self.vertical_offset_spin_spinbox.setRange(-25600, 25600)
self.vertical_offset_spin_spinbox.setSuffix("px")
- self.vertical_offset_spin_spinbox.setValue(readme_settings_async("instant_draw_notification_settings", "vertical_offset"))
- self.vertical_offset_spin_spinbox.valueChanged.connect(lambda: update_settings("instant_draw_notification_settings", "vertical_offset", self.vertical_offset_spin_spinbox.value()))
+ self.vertical_offset_spin_spinbox.setValue(
+ readme_settings_async(
+ "instant_draw_notification_settings", "vertical_offset"
+ )
+ )
+ self.vertical_offset_spin_spinbox.valueChanged.connect(
+ lambda: update_settings(
+ "instant_draw_notification_settings",
+ "vertical_offset",
+ self.vertical_offset_spin_spinbox.value(),
+ )
+ )
# 窗口透明度
self.window_transparency_spin_spinbox = SpinBox()
self.window_transparency_spin_spinbox.setFixedWidth(WIDTH_SPINBOX)
self.window_transparency_spin_spinbox.setRange(0, 100)
self.window_transparency_spin_spinbox.setSuffix("%")
- self.window_transparency_spin_spinbox.setValue(readme_settings_async("instant_draw_notification_settings", "transparency") * 100)
- self.window_transparency_spin_spinbox.valueChanged.connect(lambda: update_settings("instant_draw_notification_settings", "transparency", self.window_transparency_spin_spinbox.value() / 100))
+ self.window_transparency_spin_spinbox.setValue(
+ readme_settings_async("instant_draw_notification_settings", "transparency")
+ * 100
+ )
+ self.window_transparency_spin_spinbox.valueChanged.connect(
+ lambda: update_settings(
+ "instant_draw_notification_settings",
+ "transparency",
+ self.window_transparency_spin_spinbox.value() / 100,
+ )
+ )
# 选择启用的显示器下拉框
self.enabled_monitor_combo_box = ComboBox()
self.enabled_monitor_combo_box.addItems(self.get_monitor_list())
- if readme_settings_async("instant_draw_notification_settings", "enabled_monitor") == "OFF":
+ if (
+ readme_settings_async(
+ "instant_draw_notification_settings", "enabled_monitor"
+ )
+ == "OFF"
+ ):
self.enabled_monitor_combo_box.setCurrentText(self.get_monitor_list()[0])
- update_settings("instant_draw_notification_settings", "enabled_monitor", self.enabled_monitor_combo_box.currentText())
- self.enabled_monitor_combo_box.setCurrentText(readme_settings_async("instant_draw_notification_settings", "enabled_monitor"))
- self.enabled_monitor_combo_box.currentTextChanged.connect(lambda: self.on_first_monitor_changed(self.enabled_monitor_combo_box.currentText()))
+ update_settings(
+ "instant_draw_notification_settings",
+ "enabled_monitor",
+ self.enabled_monitor_combo_box.currentText(),
+ )
+ self.enabled_monitor_combo_box.setCurrentText(
+ readme_settings_async(
+ "instant_draw_notification_settings", "enabled_monitor"
+ )
+ )
+ self.enabled_monitor_combo_box.currentTextChanged.connect(
+ lambda: self.on_first_monitor_changed(
+ self.enabled_monitor_combo_box.currentText()
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_window_text_20_filled"),
- get_content_name_async("instant_draw_notification_settings", "enabled_monitor"), get_content_description_async("instant_draw_notification_settings", "enabled_monitor"), self.enabled_monitor_combo_box)
- self.addGroup(get_theme_icon("ic_fluent_position_to_back_20_filled"),
- get_content_name_async("instant_draw_notification_settings", "window_position"), get_content_description_async("instant_draw_notification_settings", "window_position"), self.window_position_combo_box)
- self.addGroup(get_theme_icon("ic_fluent_align_stretch_horizontal_20_filled"),
- get_content_name_async("instant_draw_notification_settings", "horizontal_offset"), get_content_description_async("instant_draw_notification_settings", "horizontal_offset"), self.horizontal_offset_spin_spinbox)
- self.addGroup(get_theme_icon("ic_fluent_align_stretch_vertical_20_filled"),
- get_content_name_async("instant_draw_notification_settings", "vertical_offset"), get_content_description_async("instant_draw_notification_settings", "vertical_offset"), self.vertical_offset_spin_spinbox)
- self.addGroup(get_theme_icon("ic_fluent_transparency_square_20_filled"),
- get_content_name_async("instant_draw_notification_settings", "transparency"), get_content_description_async("instant_draw_notification_settings", "transparency"), self.window_transparency_spin_spinbox)
+ self.addGroup(
+ get_theme_icon("ic_fluent_window_text_20_filled"),
+ get_content_name_async(
+ "instant_draw_notification_settings", "enabled_monitor"
+ ),
+ get_content_description_async(
+ "instant_draw_notification_settings", "enabled_monitor"
+ ),
+ self.enabled_monitor_combo_box,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_position_to_back_20_filled"),
+ get_content_name_async(
+ "instant_draw_notification_settings", "window_position"
+ ),
+ get_content_description_async(
+ "instant_draw_notification_settings", "window_position"
+ ),
+ self.window_position_combo_box,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_align_stretch_horizontal_20_filled"),
+ get_content_name_async(
+ "instant_draw_notification_settings", "horizontal_offset"
+ ),
+ get_content_description_async(
+ "instant_draw_notification_settings", "horizontal_offset"
+ ),
+ self.horizontal_offset_spin_spinbox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_align_stretch_vertical_20_filled"),
+ get_content_name_async(
+ "instant_draw_notification_settings", "vertical_offset"
+ ),
+ get_content_description_async(
+ "instant_draw_notification_settings", "vertical_offset"
+ ),
+ self.vertical_offset_spin_spinbox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_transparency_square_20_filled"),
+ get_content_name_async(
+ "instant_draw_notification_settings", "transparency"
+ ),
+ get_content_description_async(
+ "instant_draw_notification_settings", "transparency"
+ ),
+ self.window_transparency_spin_spinbox,
+ )
# 获取显示器列表
def get_monitor_list(self):
@@ -132,62 +280,172 @@ def get_monitor_list(self):
monitor_list.append(screen.name())
return monitor_list
+
class floating_window_settings(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("instant_draw_notification_settings", "floating_window_mode"))
+ self.setTitle(
+ get_content_name_async(
+ "instant_draw_notification_settings", "floating_window_mode"
+ )
+ )
self.setBorderRadius(8)
# 窗口位置
self.window_position_combo_box = ComboBox()
- self.window_position_combo_box.addItems(get_content_combo_name_async("instant_draw_notification_settings", "floating_window_position"))
- self.window_position_combo_box.setCurrentIndex(readme_settings_async("instant_draw_notification_settings", "floating_window_position"))
- self.window_position_combo_box.currentTextChanged.connect(lambda: update_settings("instant_draw_notification_settings", "floating_window_position", self.window_position_combo_box.currentIndex()))
-
+ self.window_position_combo_box.addItems(
+ get_content_combo_name_async(
+ "instant_draw_notification_settings", "floating_window_position"
+ )
+ )
+ self.window_position_combo_box.setCurrentIndex(
+ readme_settings_async(
+ "instant_draw_notification_settings", "floating_window_position"
+ )
+ )
+ self.window_position_combo_box.currentTextChanged.connect(
+ lambda: update_settings(
+ "instant_draw_notification_settings",
+ "floating_window_position",
+ self.window_position_combo_box.currentIndex(),
+ )
+ )
+
# 水平偏移值
self.horizontal_offset_spin_spinbox = SpinBox()
self.horizontal_offset_spin_spinbox.setFixedWidth(WIDTH_SPINBOX)
self.horizontal_offset_spin_spinbox.setRange(-25600, 25600)
self.horizontal_offset_spin_spinbox.setSuffix("px")
- self.horizontal_offset_spin_spinbox.setValue(readme_settings_async("instant_draw_notification_settings", "floating_window_horizontal_offset"))
- self.horizontal_offset_spin_spinbox.valueChanged.connect(lambda: update_settings("instant_draw_notification_settings", "floating_window_horizontal_offset", self.horizontal_offset_spin_spinbox.value()))
+ self.horizontal_offset_spin_spinbox.setValue(
+ readme_settings_async(
+ "instant_draw_notification_settings",
+ "floating_window_horizontal_offset",
+ )
+ )
+ self.horizontal_offset_spin_spinbox.valueChanged.connect(
+ lambda: update_settings(
+ "instant_draw_notification_settings",
+ "floating_window_horizontal_offset",
+ self.horizontal_offset_spin_spinbox.value(),
+ )
+ )
# 垂直偏移值
self.vertical_offset_spin_spinbox = SpinBox()
self.vertical_offset_spin_spinbox.setFixedWidth(WIDTH_SPINBOX)
self.vertical_offset_spin_spinbox.setRange(-25600, 25600)
self.vertical_offset_spin_spinbox.setSuffix("px")
- self.vertical_offset_spin_spinbox.setValue(readme_settings_async("instant_draw_notification_settings", "floating_window_vertical_offset"))
- self.vertical_offset_spin_spinbox.valueChanged.connect(lambda: update_settings("instant_draw_notification_settings", "floating_window_vertical_offset", self.vertical_offset_spin_spinbox.value()))
+ self.vertical_offset_spin_spinbox.setValue(
+ readme_settings_async(
+ "instant_draw_notification_settings", "floating_window_vertical_offset"
+ )
+ )
+ self.vertical_offset_spin_spinbox.valueChanged.connect(
+ lambda: update_settings(
+ "instant_draw_notification_settings",
+ "floating_window_vertical_offset",
+ self.vertical_offset_spin_spinbox.value(),
+ )
+ )
# 窗口透明度
self.window_transparency_spin_spinbox = SpinBox()
self.window_transparency_spin_spinbox.setFixedWidth(WIDTH_SPINBOX)
self.window_transparency_spin_spinbox.setRange(0, 100)
self.window_transparency_spin_spinbox.setSuffix("%")
- self.window_transparency_spin_spinbox.setValue(readme_settings_async("instant_draw_notification_settings", "floating_window_transparency") * 100)
- self.window_transparency_spin_spinbox.valueChanged.connect(lambda: update_settings("instant_draw_notification_settings", "floating_window_transparency", self.window_transparency_spin_spinbox.value() / 100))
+ self.window_transparency_spin_spinbox.setValue(
+ readme_settings_async(
+ "instant_draw_notification_settings", "floating_window_transparency"
+ )
+ * 100
+ )
+ self.window_transparency_spin_spinbox.valueChanged.connect(
+ lambda: update_settings(
+ "instant_draw_notification_settings",
+ "floating_window_transparency",
+ self.window_transparency_spin_spinbox.value() / 100,
+ )
+ )
# 选择启用的显示器下拉框
self.enabled_monitor_combo_box = ComboBox()
self.enabled_monitor_combo_box.addItems(self.get_monitor_list())
- if readme_settings_async("instant_draw_notification_settings", "floating_window_enabled_monitor") == "OFF":
+ if (
+ readme_settings_async(
+ "instant_draw_notification_settings", "floating_window_enabled_monitor"
+ )
+ == "OFF"
+ ):
self.enabled_monitor_combo_box.setCurrentText(self.get_monitor_list()[0])
- update_settings("instant_draw_notification_settings", "floating_window_enabled_monitor", self.enabled_monitor_combo_box.currentText())
- self.enabled_monitor_combo_box.setCurrentText(readme_settings_async("instant_draw_notification_settings", "floating_window_enabled_monitor"))
- self.enabled_monitor_combo_box.currentTextChanged.connect(lambda: self.on_floating_first_monitor_changed(self.enabled_monitor_combo_box.currentText()))
-
+ update_settings(
+ "instant_draw_notification_settings",
+ "floating_window_enabled_monitor",
+ self.enabled_monitor_combo_box.currentText(),
+ )
+ self.enabled_monitor_combo_box.setCurrentText(
+ readme_settings_async(
+ "instant_draw_notification_settings", "floating_window_enabled_monitor"
+ )
+ )
+ self.enabled_monitor_combo_box.currentTextChanged.connect(
+ lambda: self.on_floating_first_monitor_changed(
+ self.enabled_monitor_combo_box.currentText()
+ )
+ )
+
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_window_text_20_filled"),
- get_content_name_async("instant_draw_notification_settings", "floating_window_enabled_monitor"), get_content_description_async("instant_draw_notification_settings", "floating_window_enabled_monitor"), self.enabled_monitor_combo_box)
- self.addGroup(get_theme_icon("ic_fluent_position_to_back_20_filled"),
- get_content_name_async("instant_draw_notification_settings", "floating_window_position"), get_content_description_async("instant_draw_notification_settings", "floating_window_position"), self.window_position_combo_box)
- self.addGroup(get_theme_icon("ic_fluent_align_stretch_horizontal_20_filled"),
- get_content_name_async("instant_draw_notification_settings", "floating_window_horizontal_offset"), get_content_description_async("instant_draw_notification_settings", "floating_window_horizontal_offset"), self.horizontal_offset_spin_spinbox)
- self.addGroup(get_theme_icon("ic_fluent_align_stretch_vertical_20_filled"),
- get_content_name_async("instant_draw_notification_settings", "floating_window_vertical_offset"), get_content_description_async("instant_draw_notification_settings", "floating_window_vertical_offset"), self.vertical_offset_spin_spinbox)
- self.addGroup(get_theme_icon("ic_fluent_transparency_square_20_filled"),
- get_content_name_async("instant_draw_notification_settings", "floating_window_transparency"), get_content_description_async("instant_draw_notification_settings", "floating_window_transparency"), self.window_transparency_spin_spinbox)
+ self.addGroup(
+ get_theme_icon("ic_fluent_window_text_20_filled"),
+ get_content_name_async(
+ "instant_draw_notification_settings", "floating_window_enabled_monitor"
+ ),
+ get_content_description_async(
+ "instant_draw_notification_settings", "floating_window_enabled_monitor"
+ ),
+ self.enabled_monitor_combo_box,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_position_to_back_20_filled"),
+ get_content_name_async(
+ "instant_draw_notification_settings", "floating_window_position"
+ ),
+ get_content_description_async(
+ "instant_draw_notification_settings", "floating_window_position"
+ ),
+ self.window_position_combo_box,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_align_stretch_horizontal_20_filled"),
+ get_content_name_async(
+ "instant_draw_notification_settings",
+ "floating_window_horizontal_offset",
+ ),
+ get_content_description_async(
+ "instant_draw_notification_settings",
+ "floating_window_horizontal_offset",
+ ),
+ self.horizontal_offset_spin_spinbox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_align_stretch_vertical_20_filled"),
+ get_content_name_async(
+ "instant_draw_notification_settings", "floating_window_vertical_offset"
+ ),
+ get_content_description_async(
+ "instant_draw_notification_settings", "floating_window_vertical_offset"
+ ),
+ self.vertical_offset_spin_spinbox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_transparency_square_20_filled"),
+ get_content_name_async(
+ "instant_draw_notification_settings", "floating_window_transparency"
+ ),
+ get_content_description_async(
+ "instant_draw_notification_settings", "floating_window_transparency"
+ ),
+ self.window_transparency_spin_spinbox,
+ )
# 获取显示器列表
def get_monitor_list(self):
diff --git a/app/view/settings/notification_settings/lottery_notification_settings.py b/app/view/settings/notification_settings/lottery_notification_settings.py
index 9487bb17..a746a9ee 100644
--- a/app/view/settings/notification_settings/lottery_notification_settings.py
+++ b/app/view/settings/notification_settings/lottery_notification_settings.py
@@ -1,16 +1,11 @@
# ==================================================
# 导入库
# ==================================================
-import json
-import os
-import sys
-import subprocess
-
-from loguru import logger
-from PyQt6.QtWidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
+
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from PySide6.QtNetwork import *
from qfluentwidgets import *
from app.tools.variable import *
@@ -20,6 +15,7 @@
from app.tools.settings_access import *
from app.Language.obtain_language import *
+
# ==================================================
# 抽奖通知设置类
# ==================================================
@@ -38,101 +34,254 @@ def __init__(self, parent=None):
# 添加窗口模式设置组件
self.window_mode_widget = window_mode(self)
self.vBoxLayout.addWidget(self.window_mode_widget)
-
+
# 添加浮窗模式设置组件
self.floating_window_widget = floating_window_settings(self)
- self.vBoxLayout.addWidget(self.floating_window_widget)
+ self.vBoxLayout.addWidget(self.floating_window_widget)
+
class basic_settings(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("lottery_notification_settings", "basic_settings"))
+ self.setTitle(
+ get_content_name_async("lottery_notification_settings", "basic_settings")
+ )
self.setBorderRadius(8)
# 是否需要调用通知服务
self.call_notification_service_switch = SwitchButton()
- self.call_notification_service_switch.setOffText(get_content_switchbutton_name_async("lottery_notification_settings", "call_notification_service", "disable"))
- self.call_notification_service_switch.setOnText(get_content_switchbutton_name_async("lottery_notification_settings", "call_notification_service", "enable"))
- self.call_notification_service_switch.setChecked(readme_settings_async("lottery_notification_settings", "call_notification_service"))
- self.call_notification_service_switch.checkedChanged.connect(lambda: update_settings("lottery_notification_settings", "call_notification_service", self.call_notification_service_switch.isChecked()))
-
+ self.call_notification_service_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "lottery_notification_settings", "call_notification_service", "disable"
+ )
+ )
+ self.call_notification_service_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "lottery_notification_settings", "call_notification_service", "enable"
+ )
+ )
+ self.call_notification_service_switch.setChecked(
+ readme_settings_async(
+ "lottery_notification_settings", "call_notification_service"
+ )
+ )
+ self.call_notification_service_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "lottery_notification_settings",
+ "call_notification_service",
+ self.call_notification_service_switch.isChecked(),
+ )
+ )
+
# 选择通知模式下拉框
self.notification_mode_combo_box = ComboBox()
- self.notification_mode_combo_box.addItems(get_content_combo_name_async("lottery_notification_settings", "notification_mode"))
- self.notification_mode_combo_box.setCurrentText(get_content_name_async("lottery_notification_settings", "notification_mode"))
- self.notification_mode_combo_box.currentIndexChanged.connect(lambda: update_settings("lottery_notification_settings", "notification_mode", self.notification_mode_combo_box.currentIndex()))
+ self.notification_mode_combo_box.addItems(
+ get_content_combo_name_async(
+ "lottery_notification_settings", "notification_mode"
+ )
+ )
+ self.notification_mode_combo_box.setCurrentText(
+ get_content_name_async("lottery_notification_settings", "notification_mode")
+ )
+ self.notification_mode_combo_box.currentIndexChanged.connect(
+ lambda: update_settings(
+ "lottery_notification_settings",
+ "notification_mode",
+ self.notification_mode_combo_box.currentIndex(),
+ )
+ )
# 是否开启动画开关
self.animation_switch = SwitchButton()
- self.animation_switch.setOffText(get_content_switchbutton_name_async("lottery_notification_settings", "animation", "disable"))
- self.animation_switch.setOnText(get_content_switchbutton_name_async("lottery_notification_settings", "animation", "enable"))
- self.animation_switch.setChecked(readme_settings_async("lottery_notification_settings", "animation"))
- self.animation_switch.checkedChanged.connect(lambda: update_settings("lottery_notification_settings", "animation", self.animation_switch.isChecked()))
+ self.animation_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "lottery_notification_settings", "animation", "disable"
+ )
+ )
+ self.animation_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "lottery_notification_settings", "animation", "enable"
+ )
+ )
+ self.animation_switch.setChecked(
+ readme_settings_async("lottery_notification_settings", "animation")
+ )
+ self.animation_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "lottery_notification_settings",
+ "animation",
+ self.animation_switch.isChecked(),
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_comment_20_filled"),
- get_content_name_async("lottery_notification_settings", "call_notification_service"), get_content_description_async("lottery_notification_settings", "call_notification_service"), self.call_notification_service_switch)
- self.addGroup(get_theme_icon("ic_fluent_comment_20_filled"),
- get_content_name_async("lottery_notification_settings", "notification_mode"), get_content_description_async("lottery_notification_settings", "notification_mode"), self.notification_mode_combo_box)
- self.addGroup(get_theme_icon("ic_fluent_sanitize_20_filled"),
- get_content_name_async("lottery_notification_settings", "animation"), get_content_description_async("lottery_notification_settings", "animation"), self.animation_switch)
+ self.addGroup(
+ get_theme_icon("ic_fluent_comment_20_filled"),
+ get_content_name_async(
+ "lottery_notification_settings", "call_notification_service"
+ ),
+ get_content_description_async(
+ "lottery_notification_settings", "call_notification_service"
+ ),
+ self.call_notification_service_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_comment_20_filled"),
+ get_content_name_async(
+ "lottery_notification_settings", "notification_mode"
+ ),
+ get_content_description_async(
+ "lottery_notification_settings", "notification_mode"
+ ),
+ self.notification_mode_combo_box,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_sanitize_20_filled"),
+ get_content_name_async("lottery_notification_settings", "animation"),
+ get_content_description_async("lottery_notification_settings", "animation"),
+ self.animation_switch,
+ )
+
class window_mode(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("lottery_notification_settings", "window_mode"))
+ self.setTitle(
+ get_content_name_async("lottery_notification_settings", "window_mode")
+ )
self.setBorderRadius(8)
-
+
# 设置窗口的显示位置下拉框
self.window_position_combo_box = ComboBox()
- self.window_position_combo_box.addItems(get_content_combo_name_async("lottery_notification_settings", "window_position"))
- self.window_position_combo_box.setCurrentIndex(readme_settings_async("lottery_notification_settings", "window_position"))
- self.window_position_combo_box.currentIndexChanged.connect(lambda: update_settings("lottery_notification_settings", "window_position", self.window_position_combo_box.currentIndex()))
+ self.window_position_combo_box.addItems(
+ get_content_combo_name_async(
+ "lottery_notification_settings", "window_position"
+ )
+ )
+ self.window_position_combo_box.setCurrentIndex(
+ readme_settings_async("lottery_notification_settings", "window_position")
+ )
+ self.window_position_combo_box.currentIndexChanged.connect(
+ lambda: update_settings(
+ "lottery_notification_settings",
+ "window_position",
+ self.window_position_combo_box.currentIndex(),
+ )
+ )
# 水平偏移值
self.horizontal_offset_spin_spinbox = SpinBox()
self.horizontal_offset_spin_spinbox.setFixedWidth(WIDTH_SPINBOX)
self.horizontal_offset_spin_spinbox.setRange(-25600, 25600)
self.horizontal_offset_spin_spinbox.setSuffix("px")
- self.horizontal_offset_spin_spinbox.setValue(readme_settings_async("lottery_notification_settings", "horizontal_offset"))
- self.horizontal_offset_spin_spinbox.valueChanged.connect(lambda: update_settings("lottery_notification_settings", "horizontal_offset", self.horizontal_offset_spin_spinbox.value()))
+ self.horizontal_offset_spin_spinbox.setValue(
+ readme_settings_async("lottery_notification_settings", "horizontal_offset")
+ )
+ self.horizontal_offset_spin_spinbox.valueChanged.connect(
+ lambda: update_settings(
+ "lottery_notification_settings",
+ "horizontal_offset",
+ self.horizontal_offset_spin_spinbox.value(),
+ )
+ )
# 垂直偏移值
self.vertical_offset_spin_spinbox = SpinBox()
self.vertical_offset_spin_spinbox.setFixedWidth(WIDTH_SPINBOX)
self.vertical_offset_spin_spinbox.setRange(-25600, 25600)
self.vertical_offset_spin_spinbox.setSuffix("px")
- self.vertical_offset_spin_spinbox.setValue(readme_settings_async("lottery_notification_settings", "vertical_offset"))
- self.vertical_offset_spin_spinbox.valueChanged.connect(lambda: update_settings("lottery_notification_settings", "vertical_offset", self.vertical_offset_spin_spinbox.value()))
+ self.vertical_offset_spin_spinbox.setValue(
+ readme_settings_async("lottery_notification_settings", "vertical_offset")
+ )
+ self.vertical_offset_spin_spinbox.valueChanged.connect(
+ lambda: update_settings(
+ "lottery_notification_settings",
+ "vertical_offset",
+ self.vertical_offset_spin_spinbox.value(),
+ )
+ )
# 窗口透明度
self.window_transparency_spin_spinbox = SpinBox()
self.window_transparency_spin_spinbox.setFixedWidth(WIDTH_SPINBOX)
self.window_transparency_spin_spinbox.setRange(0, 100)
self.window_transparency_spin_spinbox.setSuffix("%")
- self.window_transparency_spin_spinbox.setValue(readme_settings_async("lottery_notification_settings", "transparency") * 100)
- self.window_transparency_spin_spinbox.valueChanged.connect(lambda: update_settings("lottery_notification_settings", "transparency", self.window_transparency_spin_spinbox.value() / 100))
+ self.window_transparency_spin_spinbox.setValue(
+ readme_settings_async("lottery_notification_settings", "transparency") * 100
+ )
+ self.window_transparency_spin_spinbox.valueChanged.connect(
+ lambda: update_settings(
+ "lottery_notification_settings",
+ "transparency",
+ self.window_transparency_spin_spinbox.value() / 100,
+ )
+ )
# 选择启用的显示器下拉框
self.enabled_monitor_combo_box = ComboBox()
self.enabled_monitor_combo_box.addItems(self.get_monitor_list())
- if readme_settings_async("lottery_notification_settings", "enabled_monitor") == "OFF":
+ if (
+ readme_settings_async("lottery_notification_settings", "enabled_monitor")
+ == "OFF"
+ ):
self.enabled_monitor_combo_box.setCurrentText(self.get_monitor_list()[0])
- update_settings("lottery_notification_settings", "enabled_monitor", self.enabled_monitor_combo_box.currentText())
- self.enabled_monitor_combo_box.setCurrentText(readme_settings_async("lottery_notification_settings", "enabled_monitor"))
- self.enabled_monitor_combo_box.currentTextChanged.connect(lambda: self.on_first_monitor_changed(self.enabled_monitor_combo_box.currentText()))
+ update_settings(
+ "lottery_notification_settings",
+ "enabled_monitor",
+ self.enabled_monitor_combo_box.currentText(),
+ )
+ self.enabled_monitor_combo_box.setCurrentText(
+ readme_settings_async("lottery_notification_settings", "enabled_monitor")
+ )
+ self.enabled_monitor_combo_box.currentTextChanged.connect(
+ lambda: self.on_first_monitor_changed(
+ self.enabled_monitor_combo_box.currentText()
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_window_text_20_filled"),
- get_content_name_async("lottery_notification_settings", "enabled_monitor"), get_content_description_async("lottery_notification_settings", "enabled_monitor"), self.enabled_monitor_combo_box)
- self.addGroup(get_theme_icon("ic_fluent_position_to_back_20_filled"),
- get_content_name_async("lottery_notification_settings", "window_position"), get_content_description_async("lottery_notification_settings", "window_position"), self.window_position_combo_box)
- self.addGroup(get_theme_icon("ic_fluent_align_stretch_horizontal_20_filled"),
- get_content_name_async("lottery_notification_settings", "horizontal_offset"), get_content_description_async("lottery_notification_settings", "horizontal_offset"), self.horizontal_offset_spin_spinbox)
- self.addGroup(get_theme_icon("ic_fluent_align_stretch_vertical_20_filled"),
- get_content_name_async("lottery_notification_settings", "vertical_offset"), get_content_description_async("lottery_notification_settings", "vertical_offset"), self.vertical_offset_spin_spinbox)
- self.addGroup(get_theme_icon("ic_fluent_transparency_square_20_filled"),
- get_content_name_async("lottery_notification_settings", "transparency"), get_content_description_async("lottery_notification_settings", "transparency"), self.window_transparency_spin_spinbox)
+ self.addGroup(
+ get_theme_icon("ic_fluent_window_text_20_filled"),
+ get_content_name_async("lottery_notification_settings", "enabled_monitor"),
+ get_content_description_async(
+ "lottery_notification_settings", "enabled_monitor"
+ ),
+ self.enabled_monitor_combo_box,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_position_to_back_20_filled"),
+ get_content_name_async("lottery_notification_settings", "window_position"),
+ get_content_description_async(
+ "lottery_notification_settings", "window_position"
+ ),
+ self.window_position_combo_box,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_align_stretch_horizontal_20_filled"),
+ get_content_name_async(
+ "lottery_notification_settings", "horizontal_offset"
+ ),
+ get_content_description_async(
+ "lottery_notification_settings", "horizontal_offset"
+ ),
+ self.horizontal_offset_spin_spinbox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_align_stretch_vertical_20_filled"),
+ get_content_name_async("lottery_notification_settings", "vertical_offset"),
+ get_content_description_async(
+ "lottery_notification_settings", "vertical_offset"
+ ),
+ self.vertical_offset_spin_spinbox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_transparency_square_20_filled"),
+ get_content_name_async("lottery_notification_settings", "transparency"),
+ get_content_description_async(
+ "lottery_notification_settings", "transparency"
+ ),
+ self.window_transparency_spin_spinbox,
+ )
# 获取显示器列表
def get_monitor_list(self):
@@ -141,62 +290,169 @@ def get_monitor_list(self):
monitor_list.append(screen.name())
return monitor_list
+
class floating_window_settings(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("lottery_notification_settings", "floating_window_mode"))
+ self.setTitle(
+ get_content_name_async(
+ "lottery_notification_settings", "floating_window_mode"
+ )
+ )
self.setBorderRadius(8)
# 窗口位置
self.window_position_combo_box = ComboBox()
- self.window_position_combo_box.addItems(get_content_combo_name_async("lottery_notification_settings", "floating_window_position"))
- self.window_position_combo_box.setCurrentIndex(readme_settings_async("lottery_notification_settings", "floating_window_position"))
- self.window_position_combo_box.currentTextChanged.connect(lambda: update_settings("lottery_notification_settings", "floating_window_position", self.window_position_combo_box.currentIndex()))
-
+ self.window_position_combo_box.addItems(
+ get_content_combo_name_async(
+ "lottery_notification_settings", "floating_window_position"
+ )
+ )
+ self.window_position_combo_box.setCurrentIndex(
+ readme_settings_async(
+ "lottery_notification_settings", "floating_window_position"
+ )
+ )
+ self.window_position_combo_box.currentTextChanged.connect(
+ lambda: update_settings(
+ "lottery_notification_settings",
+ "floating_window_position",
+ self.window_position_combo_box.currentIndex(),
+ )
+ )
+
# 水平偏移值
self.horizontal_offset_spin_spinbox = SpinBox()
self.horizontal_offset_spin_spinbox.setFixedWidth(WIDTH_SPINBOX)
self.horizontal_offset_spin_spinbox.setRange(-25600, 25600)
self.horizontal_offset_spin_spinbox.setSuffix("px")
- self.horizontal_offset_spin_spinbox.setValue(readme_settings_async("lottery_notification_settings", "floating_window_horizontal_offset"))
- self.horizontal_offset_spin_spinbox.valueChanged.connect(lambda: update_settings("lottery_notification_settings", "floating_window_horizontal_offset", self.horizontal_offset_spin_spinbox.value()))
+ self.horizontal_offset_spin_spinbox.setValue(
+ readme_settings_async(
+ "lottery_notification_settings", "floating_window_horizontal_offset"
+ )
+ )
+ self.horizontal_offset_spin_spinbox.valueChanged.connect(
+ lambda: update_settings(
+ "lottery_notification_settings",
+ "floating_window_horizontal_offset",
+ self.horizontal_offset_spin_spinbox.value(),
+ )
+ )
# 垂直偏移值
self.vertical_offset_spin_spinbox = SpinBox()
self.vertical_offset_spin_spinbox.setFixedWidth(WIDTH_SPINBOX)
self.vertical_offset_spin_spinbox.setRange(-25600, 25600)
self.vertical_offset_spin_spinbox.setSuffix("px")
- self.vertical_offset_spin_spinbox.setValue(readme_settings_async("lottery_notification_settings", "floating_window_vertical_offset"))
- self.vertical_offset_spin_spinbox.valueChanged.connect(lambda: update_settings("lottery_notification_settings", "floating_window_vertical_offset", self.vertical_offset_spin_spinbox.value()))
+ self.vertical_offset_spin_spinbox.setValue(
+ readme_settings_async(
+ "lottery_notification_settings", "floating_window_vertical_offset"
+ )
+ )
+ self.vertical_offset_spin_spinbox.valueChanged.connect(
+ lambda: update_settings(
+ "lottery_notification_settings",
+ "floating_window_vertical_offset",
+ self.vertical_offset_spin_spinbox.value(),
+ )
+ )
# 窗口透明度
self.window_transparency_spin_spinbox = SpinBox()
self.window_transparency_spin_spinbox.setFixedWidth(WIDTH_SPINBOX)
self.window_transparency_spin_spinbox.setRange(0, 100)
self.window_transparency_spin_spinbox.setSuffix("%")
- self.window_transparency_spin_spinbox.setValue(readme_settings_async("lottery_notification_settings", "floating_window_transparency") * 100)
- self.window_transparency_spin_spinbox.valueChanged.connect(lambda: update_settings("lottery_notification_settings", "floating_window_transparency", self.window_transparency_spin_spinbox.value() / 100))
+ self.window_transparency_spin_spinbox.setValue(
+ readme_settings_async(
+ "lottery_notification_settings", "floating_window_transparency"
+ )
+ * 100
+ )
+ self.window_transparency_spin_spinbox.valueChanged.connect(
+ lambda: update_settings(
+ "lottery_notification_settings",
+ "floating_window_transparency",
+ self.window_transparency_spin_spinbox.value() / 100,
+ )
+ )
# 选择启用的显示器下拉框
self.enabled_monitor_combo_box = ComboBox()
self.enabled_monitor_combo_box.addItems(self.get_monitor_list())
- if readme_settings_async("lottery_notification_settings", "floating_window_enabled_monitor") == "OFF":
+ if (
+ readme_settings_async(
+ "lottery_notification_settings", "floating_window_enabled_monitor"
+ )
+ == "OFF"
+ ):
self.enabled_monitor_combo_box.setCurrentText(self.get_monitor_list()[0])
- update_settings("lottery_notification_settings", "floating_window_enabled_monitor", self.enabled_monitor_combo_box.currentText())
- self.enabled_monitor_combo_box.setCurrentText(readme_settings_async("lottery_notification_settings", "floating_window_enabled_monitor"))
- self.enabled_monitor_combo_box.currentTextChanged.connect(lambda: self.on_floating_first_monitor_changed(self.enabled_monitor_combo_box.currentText()))
-
+ update_settings(
+ "lottery_notification_settings",
+ "floating_window_enabled_monitor",
+ self.enabled_monitor_combo_box.currentText(),
+ )
+ self.enabled_monitor_combo_box.setCurrentText(
+ readme_settings_async(
+ "lottery_notification_settings", "floating_window_enabled_monitor"
+ )
+ )
+ self.enabled_monitor_combo_box.currentTextChanged.connect(
+ lambda: self.on_floating_first_monitor_changed(
+ self.enabled_monitor_combo_box.currentText()
+ )
+ )
+
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_window_text_20_filled"),
- get_content_name_async("lottery_notification_settings", "floating_window_enabled_monitor"), get_content_description_async("lottery_notification_settings", "floating_window_enabled_monitor"), self.enabled_monitor_combo_box)
- self.addGroup(get_theme_icon("ic_fluent_position_to_back_20_filled"),
- get_content_name_async("lottery_notification_settings", "floating_window_position"), get_content_description_async("lottery_notification_settings", "floating_window_position"), self.window_position_combo_box)
- self.addGroup(get_theme_icon("ic_fluent_align_stretch_horizontal_20_filled"),
- get_content_name_async("lottery_notification_settings", "floating_window_horizontal_offset"), get_content_description_async("lottery_notification_settings", "floating_window_horizontal_offset"), self.horizontal_offset_spin_spinbox)
- self.addGroup(get_theme_icon("ic_fluent_align_stretch_vertical_20_filled"),
- get_content_name_async("lottery_notification_settings", "floating_window_vertical_offset"), get_content_description_async("lottery_notification_settings", "floating_window_vertical_offset"), self.vertical_offset_spin_spinbox)
- self.addGroup(get_theme_icon("ic_fluent_transparency_square_20_filled"),
- get_content_name_async("lottery_notification_settings", "floating_window_transparency"), get_content_description_async("lottery_notification_settings", "floating_window_transparency"), self.window_transparency_spin_spinbox)
+ self.addGroup(
+ get_theme_icon("ic_fluent_window_text_20_filled"),
+ get_content_name_async(
+ "lottery_notification_settings", "floating_window_enabled_monitor"
+ ),
+ get_content_description_async(
+ "lottery_notification_settings", "floating_window_enabled_monitor"
+ ),
+ self.enabled_monitor_combo_box,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_position_to_back_20_filled"),
+ get_content_name_async(
+ "lottery_notification_settings", "floating_window_position"
+ ),
+ get_content_description_async(
+ "lottery_notification_settings", "floating_window_position"
+ ),
+ self.window_position_combo_box,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_align_stretch_horizontal_20_filled"),
+ get_content_name_async(
+ "lottery_notification_settings", "floating_window_horizontal_offset"
+ ),
+ get_content_description_async(
+ "lottery_notification_settings", "floating_window_horizontal_offset"
+ ),
+ self.horizontal_offset_spin_spinbox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_align_stretch_vertical_20_filled"),
+ get_content_name_async(
+ "lottery_notification_settings", "floating_window_vertical_offset"
+ ),
+ get_content_description_async(
+ "lottery_notification_settings", "floating_window_vertical_offset"
+ ),
+ self.vertical_offset_spin_spinbox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_transparency_square_20_filled"),
+ get_content_name_async(
+ "lottery_notification_settings", "floating_window_transparency"
+ ),
+ get_content_description_async(
+ "lottery_notification_settings", "floating_window_transparency"
+ ),
+ self.window_transparency_spin_spinbox,
+ )
# 获取显示器列表
def get_monitor_list(self):
diff --git a/app/view/settings/notification_settings/more_notification_settings.py b/app/view/settings/notification_settings/more_notification_settings.py
index 5b47e115..775cf007 100644
--- a/app/view/settings/notification_settings/more_notification_settings.py
+++ b/app/view/settings/notification_settings/more_notification_settings.py
@@ -1,16 +1,11 @@
# ==================================================
# 导入库
# ==================================================
-import json
-import os
-import sys
-import subprocess
-from loguru import logger
-from PyQt6.QtWidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from PySide6.QtNetwork import *
from qfluentwidgets import *
from app.tools.variable import *
diff --git a/app/view/settings/notification_settings/quick_draw_notification_settings.py b/app/view/settings/notification_settings/quick_draw_notification_settings.py
index b2869af7..bd81d9b8 100644
--- a/app/view/settings/notification_settings/quick_draw_notification_settings.py
+++ b/app/view/settings/notification_settings/quick_draw_notification_settings.py
@@ -1,16 +1,11 @@
# ==================================================
# 导入库
# ==================================================
-import json
-import os
-import sys
-import subprocess
-
-from loguru import logger
-from PyQt6.QtWidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
+
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from PySide6.QtNetwork import *
from qfluentwidgets import *
from app.tools.variable import *
@@ -20,6 +15,7 @@
from app.tools.settings_access import *
from app.Language.obtain_language import *
+
# ==================================================
# 闪抽通知设置类
# ==================================================
@@ -38,92 +34,232 @@ def __init__(self, parent=None):
# 添加窗口模式设置组件
self.window_mode_widget = window_mode(self)
self.vBoxLayout.addWidget(self.window_mode_widget)
-
+
# 添加浮窗模式设置组件
self.floating_window_widget = floating_window_settings(self)
- self.vBoxLayout.addWidget(self.floating_window_widget)
+ self.vBoxLayout.addWidget(self.floating_window_widget)
+
class basic_settings(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("quick_draw_notification_settings", "basic_settings"))
+ self.setTitle(
+ get_content_name_async("quick_draw_notification_settings", "basic_settings")
+ )
self.setBorderRadius(8)
-
+
# 选择通知模式下拉框
self.notification_mode_combo_box = ComboBox()
- self.notification_mode_combo_box.addItems(get_content_combo_name_async("quick_draw_notification_settings", "notification_mode"))
- self.notification_mode_combo_box.setCurrentText(get_content_name_async("quick_draw_notification_settings", "notification_mode"))
- self.notification_mode_combo_box.currentIndexChanged.connect(lambda: update_settings("quick_draw_notification_settings", "notification_mode", self.notification_mode_combo_box.currentIndex()))
+ self.notification_mode_combo_box.addItems(
+ get_content_combo_name_async(
+ "quick_draw_notification_settings", "notification_mode"
+ )
+ )
+ self.notification_mode_combo_box.setCurrentText(
+ get_content_name_async(
+ "quick_draw_notification_settings", "notification_mode"
+ )
+ )
+ self.notification_mode_combo_box.currentIndexChanged.connect(
+ lambda: update_settings(
+ "quick_draw_notification_settings",
+ "notification_mode",
+ self.notification_mode_combo_box.currentIndex(),
+ )
+ )
# 是否开启动画开关
self.animation_switch = SwitchButton()
- self.animation_switch.setOffText(get_content_switchbutton_name_async("quick_draw_notification_settings", "animation", "disable"))
- self.animation_switch.setOnText(get_content_switchbutton_name_async("quick_draw_notification_settings", "animation", "enable"))
- self.animation_switch.setChecked(readme_settings_async("quick_draw_notification_settings", "animation"))
- self.animation_switch.checkedChanged.connect(lambda: update_settings("quick_draw_notification_settings", "animation", self.animation_switch.isChecked()))
+ self.animation_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "quick_draw_notification_settings", "animation", "disable"
+ )
+ )
+ self.animation_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "quick_draw_notification_settings", "animation", "enable"
+ )
+ )
+ self.animation_switch.setChecked(
+ readme_settings_async("quick_draw_notification_settings", "animation")
+ )
+ self.animation_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "quick_draw_notification_settings",
+ "animation",
+ self.animation_switch.isChecked(),
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_comment_20_filled"),
- get_content_name_async("quick_draw_notification_settings", "notification_mode"), get_content_description_async("quick_draw_notification_settings", "notification_mode"), self.notification_mode_combo_box)
- self.addGroup(get_theme_icon("ic_fluent_sanitize_20_filled"),
- get_content_name_async("quick_draw_notification_settings", "animation"), get_content_description_async("quick_draw_notification_settings", "animation"), self.animation_switch)
+ self.addGroup(
+ get_theme_icon("ic_fluent_comment_20_filled"),
+ get_content_name_async(
+ "quick_draw_notification_settings", "notification_mode"
+ ),
+ get_content_description_async(
+ "quick_draw_notification_settings", "notification_mode"
+ ),
+ self.notification_mode_combo_box,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_sanitize_20_filled"),
+ get_content_name_async("quick_draw_notification_settings", "animation"),
+ get_content_description_async(
+ "quick_draw_notification_settings", "animation"
+ ),
+ self.animation_switch,
+ )
+
class window_mode(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("quick_draw_notification_settings", "window_mode"))
+ self.setTitle(
+ get_content_name_async("quick_draw_notification_settings", "window_mode")
+ )
self.setBorderRadius(8)
-
+
# 设置窗口的显示位置下拉框
self.window_position_combo_box = ComboBox()
- self.window_position_combo_box.addItems(get_content_combo_name_async("quick_draw_notification_settings", "window_position"))
- self.window_position_combo_box.setCurrentIndex(readme_settings_async("quick_draw_notification_settings", "window_position"))
- self.window_position_combo_box.currentIndexChanged.connect(lambda: update_settings("quick_draw_notification_settings", "window_position", self.window_position_combo_box.currentIndex()))
+ self.window_position_combo_box.addItems(
+ get_content_combo_name_async(
+ "quick_draw_notification_settings", "window_position"
+ )
+ )
+ self.window_position_combo_box.setCurrentIndex(
+ readme_settings_async("quick_draw_notification_settings", "window_position")
+ )
+ self.window_position_combo_box.currentIndexChanged.connect(
+ lambda: update_settings(
+ "quick_draw_notification_settings",
+ "window_position",
+ self.window_position_combo_box.currentIndex(),
+ )
+ )
# 水平偏移值
self.horizontal_offset_spin_spinbox = SpinBox()
self.horizontal_offset_spin_spinbox.setFixedWidth(WIDTH_SPINBOX)
self.horizontal_offset_spin_spinbox.setRange(-25600, 25600)
self.horizontal_offset_spin_spinbox.setSuffix("px")
- self.horizontal_offset_spin_spinbox.setValue(readme_settings_async("quick_draw_notification_settings", "horizontal_offset"))
- self.horizontal_offset_spin_spinbox.valueChanged.connect(lambda: update_settings("quick_draw_notification_settings", "horizontal_offset", self.horizontal_offset_spin_spinbox.value()))
+ self.horizontal_offset_spin_spinbox.setValue(
+ readme_settings_async(
+ "quick_draw_notification_settings", "horizontal_offset"
+ )
+ )
+ self.horizontal_offset_spin_spinbox.valueChanged.connect(
+ lambda: update_settings(
+ "quick_draw_notification_settings",
+ "horizontal_offset",
+ self.horizontal_offset_spin_spinbox.value(),
+ )
+ )
# 垂直偏移值
self.vertical_offset_spin_spinbox = SpinBox()
self.vertical_offset_spin_spinbox.setFixedWidth(WIDTH_SPINBOX)
self.vertical_offset_spin_spinbox.setRange(-25600, 25600)
self.vertical_offset_spin_spinbox.setSuffix("px")
- self.vertical_offset_spin_spinbox.setValue(readme_settings_async("quick_draw_notification_settings", "vertical_offset"))
- self.vertical_offset_spin_spinbox.valueChanged.connect(lambda: update_settings("quick_draw_notification_settings", "vertical_offset", self.vertical_offset_spin_spinbox.value()))
+ self.vertical_offset_spin_spinbox.setValue(
+ readme_settings_async("quick_draw_notification_settings", "vertical_offset")
+ )
+ self.vertical_offset_spin_spinbox.valueChanged.connect(
+ lambda: update_settings(
+ "quick_draw_notification_settings",
+ "vertical_offset",
+ self.vertical_offset_spin_spinbox.value(),
+ )
+ )
# 窗口透明度
self.window_transparency_spin_spinbox = SpinBox()
self.window_transparency_spin_spinbox.setFixedWidth(WIDTH_SPINBOX)
self.window_transparency_spin_spinbox.setRange(0, 100)
self.window_transparency_spin_spinbox.setSuffix("%")
- self.window_transparency_spin_spinbox.setValue(readme_settings_async("quick_draw_notification_settings", "transparency") * 100)
- self.window_transparency_spin_spinbox.valueChanged.connect(lambda: update_settings("quick_draw_notification_settings", "transparency", self.window_transparency_spin_spinbox.value() / 100))
+ self.window_transparency_spin_spinbox.setValue(
+ readme_settings_async("quick_draw_notification_settings", "transparency")
+ * 100
+ )
+ self.window_transparency_spin_spinbox.valueChanged.connect(
+ lambda: update_settings(
+ "quick_draw_notification_settings",
+ "transparency",
+ self.window_transparency_spin_spinbox.value() / 100,
+ )
+ )
# 选择启用的显示器下拉框
self.enabled_monitor_combo_box = ComboBox()
self.enabled_monitor_combo_box.addItems(self.get_monitor_list())
- if readme_settings_async("quick_draw_notification_settings", "enabled_monitor") == "OFF":
+ if (
+ readme_settings_async("quick_draw_notification_settings", "enabled_monitor")
+ == "OFF"
+ ):
self.enabled_monitor_combo_box.setCurrentText(self.get_monitor_list()[0])
- update_settings("quick_draw_notification_settings", "enabled_monitor", self.enabled_monitor_combo_box.currentText())
- self.enabled_monitor_combo_box.setCurrentText(readme_settings_async("quick_draw_notification_settings", "enabled_monitor"))
- self.enabled_monitor_combo_box.currentTextChanged.connect(lambda: self.on_first_monitor_changed(self.enabled_monitor_combo_box.currentText()))
+ update_settings(
+ "quick_draw_notification_settings",
+ "enabled_monitor",
+ self.enabled_monitor_combo_box.currentText(),
+ )
+ self.enabled_monitor_combo_box.setCurrentText(
+ readme_settings_async("quick_draw_notification_settings", "enabled_monitor")
+ )
+ self.enabled_monitor_combo_box.currentTextChanged.connect(
+ lambda: self.on_first_monitor_changed(
+ self.enabled_monitor_combo_box.currentText()
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_window_text_20_filled"),
- get_content_name_async("quick_draw_notification_settings", "enabled_monitor"), get_content_description_async("quick_draw_notification_settings", "enabled_monitor"), self.enabled_monitor_combo_box)
- self.addGroup(get_theme_icon("ic_fluent_position_to_back_20_filled"),
- get_content_name_async("quick_draw_notification_settings", "window_position"), get_content_description_async("quick_draw_notification_settings", "window_position"), self.window_position_combo_box)
- self.addGroup(get_theme_icon("ic_fluent_align_stretch_horizontal_20_filled"),
- get_content_name_async("quick_draw_notification_settings", "horizontal_offset"), get_content_description_async("quick_draw_notification_settings", "horizontal_offset"), self.horizontal_offset_spin_spinbox)
- self.addGroup(get_theme_icon("ic_fluent_align_stretch_vertical_20_filled"),
- get_content_name_async("quick_draw_notification_settings", "vertical_offset"), get_content_description_async("quick_draw_notification_settings", "vertical_offset"), self.vertical_offset_spin_spinbox)
- self.addGroup(get_theme_icon("ic_fluent_transparency_square_20_filled"),
- get_content_name_async("quick_draw_notification_settings", "transparency"), get_content_description_async("quick_draw_notification_settings", "transparency"), self.window_transparency_spin_spinbox)
+ self.addGroup(
+ get_theme_icon("ic_fluent_window_text_20_filled"),
+ get_content_name_async(
+ "quick_draw_notification_settings", "enabled_monitor"
+ ),
+ get_content_description_async(
+ "quick_draw_notification_settings", "enabled_monitor"
+ ),
+ self.enabled_monitor_combo_box,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_position_to_back_20_filled"),
+ get_content_name_async(
+ "quick_draw_notification_settings", "window_position"
+ ),
+ get_content_description_async(
+ "quick_draw_notification_settings", "window_position"
+ ),
+ self.window_position_combo_box,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_align_stretch_horizontal_20_filled"),
+ get_content_name_async(
+ "quick_draw_notification_settings", "horizontal_offset"
+ ),
+ get_content_description_async(
+ "quick_draw_notification_settings", "horizontal_offset"
+ ),
+ self.horizontal_offset_spin_spinbox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_align_stretch_vertical_20_filled"),
+ get_content_name_async(
+ "quick_draw_notification_settings", "vertical_offset"
+ ),
+ get_content_description_async(
+ "quick_draw_notification_settings", "vertical_offset"
+ ),
+ self.vertical_offset_spin_spinbox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_transparency_square_20_filled"),
+ get_content_name_async("quick_draw_notification_settings", "transparency"),
+ get_content_description_async(
+ "quick_draw_notification_settings", "transparency"
+ ),
+ self.window_transparency_spin_spinbox,
+ )
# 获取显示器列表
def get_monitor_list(self):
@@ -132,62 +268,169 @@ def get_monitor_list(self):
monitor_list.append(screen.name())
return monitor_list
+
class floating_window_settings(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("quick_draw_notification_settings", "floating_window_mode"))
+ self.setTitle(
+ get_content_name_async(
+ "quick_draw_notification_settings", "floating_window_mode"
+ )
+ )
self.setBorderRadius(8)
# 窗口位置
self.window_position_combo_box = ComboBox()
- self.window_position_combo_box.addItems(get_content_combo_name_async("quick_draw_notification_settings", "floating_window_position"))
- self.window_position_combo_box.setCurrentIndex(readme_settings_async("quick_draw_notification_settings", "floating_window_position"))
- self.window_position_combo_box.currentTextChanged.connect(lambda: update_settings("quick_draw_notification_settings", "floating_window_position", self.window_position_combo_box.currentIndex()))
-
+ self.window_position_combo_box.addItems(
+ get_content_combo_name_async(
+ "quick_draw_notification_settings", "floating_window_position"
+ )
+ )
+ self.window_position_combo_box.setCurrentIndex(
+ readme_settings_async(
+ "quick_draw_notification_settings", "floating_window_position"
+ )
+ )
+ self.window_position_combo_box.currentTextChanged.connect(
+ lambda: update_settings(
+ "quick_draw_notification_settings",
+ "floating_window_position",
+ self.window_position_combo_box.currentIndex(),
+ )
+ )
+
# 水平偏移值
self.horizontal_offset_spin_spinbox = SpinBox()
self.horizontal_offset_spin_spinbox.setFixedWidth(WIDTH_SPINBOX)
self.horizontal_offset_spin_spinbox.setRange(-25600, 25600)
self.horizontal_offset_spin_spinbox.setSuffix("px")
- self.horizontal_offset_spin_spinbox.setValue(readme_settings_async("quick_draw_notification_settings", "floating_window_horizontal_offset"))
- self.horizontal_offset_spin_spinbox.valueChanged.connect(lambda: update_settings("quick_draw_notification_settings", "floating_window_horizontal_offset", self.horizontal_offset_spin_spinbox.value()))
+ self.horizontal_offset_spin_spinbox.setValue(
+ readme_settings_async(
+ "quick_draw_notification_settings", "floating_window_horizontal_offset"
+ )
+ )
+ self.horizontal_offset_spin_spinbox.valueChanged.connect(
+ lambda: update_settings(
+ "quick_draw_notification_settings",
+ "floating_window_horizontal_offset",
+ self.horizontal_offset_spin_spinbox.value(),
+ )
+ )
# 垂直偏移值
self.vertical_offset_spin_spinbox = SpinBox()
self.vertical_offset_spin_spinbox.setFixedWidth(WIDTH_SPINBOX)
self.vertical_offset_spin_spinbox.setRange(-25600, 25600)
self.vertical_offset_spin_spinbox.setSuffix("px")
- self.vertical_offset_spin_spinbox.setValue(readme_settings_async("quick_draw_notification_settings", "floating_window_vertical_offset"))
- self.vertical_offset_spin_spinbox.valueChanged.connect(lambda: update_settings("quick_draw_notification_settings", "floating_window_vertical_offset", self.vertical_offset_spin_spinbox.value()))
+ self.vertical_offset_spin_spinbox.setValue(
+ readme_settings_async(
+ "quick_draw_notification_settings", "floating_window_vertical_offset"
+ )
+ )
+ self.vertical_offset_spin_spinbox.valueChanged.connect(
+ lambda: update_settings(
+ "quick_draw_notification_settings",
+ "floating_window_vertical_offset",
+ self.vertical_offset_spin_spinbox.value(),
+ )
+ )
# 窗口透明度
self.window_transparency_spin_spinbox = SpinBox()
self.window_transparency_spin_spinbox.setFixedWidth(WIDTH_SPINBOX)
self.window_transparency_spin_spinbox.setRange(0, 100)
self.window_transparency_spin_spinbox.setSuffix("%")
- self.window_transparency_spin_spinbox.setValue(readme_settings_async("quick_draw_notification_settings", "floating_window_transparency") * 100)
- self.window_transparency_spin_spinbox.valueChanged.connect(lambda: update_settings("quick_draw_notification_settings", "floating_window_transparency", self.window_transparency_spin_spinbox.value() / 100))
+ self.window_transparency_spin_spinbox.setValue(
+ readme_settings_async(
+ "quick_draw_notification_settings", "floating_window_transparency"
+ )
+ * 100
+ )
+ self.window_transparency_spin_spinbox.valueChanged.connect(
+ lambda: update_settings(
+ "quick_draw_notification_settings",
+ "floating_window_transparency",
+ self.window_transparency_spin_spinbox.value() / 100,
+ )
+ )
# 选择启用的显示器下拉框
self.enabled_monitor_combo_box = ComboBox()
self.enabled_monitor_combo_box.addItems(self.get_monitor_list())
- if readme_settings_async("quick_draw_notification_settings", "floating_window_enabled_monitor") == "OFF":
+ if (
+ readme_settings_async(
+ "quick_draw_notification_settings", "floating_window_enabled_monitor"
+ )
+ == "OFF"
+ ):
self.enabled_monitor_combo_box.setCurrentText(self.get_monitor_list()[0])
- update_settings("quick_draw_notification_settings", "floating_window_enabled_monitor", self.enabled_monitor_combo_box.currentText())
- self.enabled_monitor_combo_box.setCurrentText(readme_settings_async("quick_draw_notification_settings", "floating_window_enabled_monitor"))
- self.enabled_monitor_combo_box.currentTextChanged.connect(lambda: self.on_floating_first_monitor_changed(self.enabled_monitor_combo_box.currentText()))
-
+ update_settings(
+ "quick_draw_notification_settings",
+ "floating_window_enabled_monitor",
+ self.enabled_monitor_combo_box.currentText(),
+ )
+ self.enabled_monitor_combo_box.setCurrentText(
+ readme_settings_async(
+ "quick_draw_notification_settings", "floating_window_enabled_monitor"
+ )
+ )
+ self.enabled_monitor_combo_box.currentTextChanged.connect(
+ lambda: self.on_floating_first_monitor_changed(
+ self.enabled_monitor_combo_box.currentText()
+ )
+ )
+
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_window_text_20_filled"),
- get_content_name_async("quick_draw_notification_settings", "floating_window_enabled_monitor"), get_content_description_async("quick_draw_notification_settings", "floating_window_enabled_monitor"), self.enabled_monitor_combo_box)
- self.addGroup(get_theme_icon("ic_fluent_position_to_back_20_filled"),
- get_content_name_async("quick_draw_notification_settings", "floating_window_position"), get_content_description_async("quick_draw_notification_settings", "floating_window_position"), self.window_position_combo_box)
- self.addGroup(get_theme_icon("ic_fluent_align_stretch_horizontal_20_filled"),
- get_content_name_async("quick_draw_notification_settings", "floating_window_horizontal_offset"), get_content_description_async("quick_draw_notification_settings", "floating_window_horizontal_offset"), self.horizontal_offset_spin_spinbox)
- self.addGroup(get_theme_icon("ic_fluent_align_stretch_vertical_20_filled"),
- get_content_name_async("quick_draw_notification_settings", "floating_window_vertical_offset"), get_content_description_async("quick_draw_notification_settings", "floating_window_vertical_offset"), self.vertical_offset_spin_spinbox)
- self.addGroup(get_theme_icon("ic_fluent_transparency_square_20_filled"),
- get_content_name_async("quick_draw_notification_settings", "floating_window_transparency"), get_content_description_async("quick_draw_notification_settings", "floating_window_transparency"), self.window_transparency_spin_spinbox)
+ self.addGroup(
+ get_theme_icon("ic_fluent_window_text_20_filled"),
+ get_content_name_async(
+ "quick_draw_notification_settings", "floating_window_enabled_monitor"
+ ),
+ get_content_description_async(
+ "quick_draw_notification_settings", "floating_window_enabled_monitor"
+ ),
+ self.enabled_monitor_combo_box,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_position_to_back_20_filled"),
+ get_content_name_async(
+ "quick_draw_notification_settings", "floating_window_position"
+ ),
+ get_content_description_async(
+ "quick_draw_notification_settings", "floating_window_position"
+ ),
+ self.window_position_combo_box,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_align_stretch_horizontal_20_filled"),
+ get_content_name_async(
+ "quick_draw_notification_settings", "floating_window_horizontal_offset"
+ ),
+ get_content_description_async(
+ "quick_draw_notification_settings", "floating_window_horizontal_offset"
+ ),
+ self.horizontal_offset_spin_spinbox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_align_stretch_vertical_20_filled"),
+ get_content_name_async(
+ "quick_draw_notification_settings", "floating_window_vertical_offset"
+ ),
+ get_content_description_async(
+ "quick_draw_notification_settings", "floating_window_vertical_offset"
+ ),
+ self.vertical_offset_spin_spinbox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_transparency_square_20_filled"),
+ get_content_name_async(
+ "quick_draw_notification_settings", "floating_window_transparency"
+ ),
+ get_content_description_async(
+ "quick_draw_notification_settings", "floating_window_transparency"
+ ),
+ self.window_transparency_spin_spinbox,
+ )
# 获取显示器列表
def get_monitor_list(self):
diff --git a/app/view/settings/notification_settings/roll_call_notification_settings.py b/app/view/settings/notification_settings/roll_call_notification_settings.py
index b759501c..776c06aa 100644
--- a/app/view/settings/notification_settings/roll_call_notification_settings.py
+++ b/app/view/settings/notification_settings/roll_call_notification_settings.py
@@ -1,16 +1,11 @@
# ==================================================
# 导入库
# ==================================================
-import json
-import os
-import sys
-import subprocess
-
-from loguru import logger
-from PyQt6.QtWidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
+
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from PySide6.QtNetwork import *
from qfluentwidgets import *
from app.tools.variable import *
@@ -20,6 +15,7 @@
from app.tools.settings_access import *
from app.Language.obtain_language import *
+
# ==================================================
# 点名通知设置
# ==================================================
@@ -38,101 +34,269 @@ def __init__(self, parent=None):
# 添加窗口模式设置组件
self.window_mode_widget = window_mode(self)
self.vBoxLayout.addWidget(self.window_mode_widget)
-
+
# 添加浮窗模式设置组件
self.floating_window_widget = floating_window_settings(self)
- self.vBoxLayout.addWidget(self.floating_window_widget)
+ self.vBoxLayout.addWidget(self.floating_window_widget)
+
class basic_settings(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("roll_call_notification_settings", "basic_settings"))
+ self.setTitle(
+ get_content_name_async("roll_call_notification_settings", "basic_settings")
+ )
self.setBorderRadius(8)
# 是否需要调用通知服务
self.call_notification_service_switch = SwitchButton()
- self.call_notification_service_switch.setOffText(get_content_switchbutton_name_async("roll_call_notification_settings", "call_notification_service", "disable"))
- self.call_notification_service_switch.setOnText(get_content_switchbutton_name_async("roll_call_notification_settings", "call_notification_service", "enable"))
- self.call_notification_service_switch.setChecked(readme_settings_async("roll_call_notification_settings", "call_notification_service"))
- self.call_notification_service_switch.checkedChanged.connect(lambda: update_settings("roll_call_notification_settings", "call_notification_service", self.call_notification_service_switch.isChecked()))
-
+ self.call_notification_service_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "roll_call_notification_settings",
+ "call_notification_service",
+ "disable",
+ )
+ )
+ self.call_notification_service_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "roll_call_notification_settings", "call_notification_service", "enable"
+ )
+ )
+ self.call_notification_service_switch.setChecked(
+ readme_settings_async(
+ "roll_call_notification_settings", "call_notification_service"
+ )
+ )
+ self.call_notification_service_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "roll_call_notification_settings",
+ "call_notification_service",
+ self.call_notification_service_switch.isChecked(),
+ )
+ )
+
# 选择通知模式下拉框
self.notification_mode_combo_box = ComboBox()
- self.notification_mode_combo_box.addItems(get_content_combo_name_async("roll_call_notification_settings", "notification_mode"))
- self.notification_mode_combo_box.setCurrentText(get_content_name_async("roll_call_notification_settings", "notification_mode"))
- self.notification_mode_combo_box.currentIndexChanged.connect(lambda: update_settings("roll_call_notification_settings", "notification_mode", self.notification_mode_combo_box.currentIndex()))
+ self.notification_mode_combo_box.addItems(
+ get_content_combo_name_async(
+ "roll_call_notification_settings", "notification_mode"
+ )
+ )
+ self.notification_mode_combo_box.setCurrentText(
+ get_content_name_async(
+ "roll_call_notification_settings", "notification_mode"
+ )
+ )
+ self.notification_mode_combo_box.currentIndexChanged.connect(
+ lambda: update_settings(
+ "roll_call_notification_settings",
+ "notification_mode",
+ self.notification_mode_combo_box.currentIndex(),
+ )
+ )
# 是否开启动画开关
self.animation_switch = SwitchButton()
- self.animation_switch.setOffText(get_content_switchbutton_name_async("roll_call_notification_settings", "animation", "disable"))
- self.animation_switch.setOnText(get_content_switchbutton_name_async("roll_call_notification_settings", "animation", "enable"))
- self.animation_switch.setChecked(readme_settings_async("roll_call_notification_settings", "animation"))
- self.animation_switch.checkedChanged.connect(lambda: update_settings("roll_call_notification_settings", "animation", self.animation_switch.isChecked()))
+ self.animation_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "roll_call_notification_settings", "animation", "disable"
+ )
+ )
+ self.animation_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "roll_call_notification_settings", "animation", "enable"
+ )
+ )
+ self.animation_switch.setChecked(
+ readme_settings_async("roll_call_notification_settings", "animation")
+ )
+ self.animation_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "roll_call_notification_settings",
+ "animation",
+ self.animation_switch.isChecked(),
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_comment_20_filled"),
- get_content_name_async("roll_call_notification_settings", "call_notification_service"), get_content_description_async("roll_call_notification_settings", "call_notification_service"), self.call_notification_service_switch)
- self.addGroup(get_theme_icon("ic_fluent_comment_20_filled"),
- get_content_name_async("roll_call_notification_settings", "notification_mode"), get_content_description_async("roll_call_notification_settings", "notification_mode"), self.notification_mode_combo_box)
- self.addGroup(get_theme_icon("ic_fluent_sanitize_20_filled"),
- get_content_name_async("roll_call_notification_settings", "animation"), get_content_description_async("roll_call_notification_settings", "animation"), self.animation_switch)
+ self.addGroup(
+ get_theme_icon("ic_fluent_comment_20_filled"),
+ get_content_name_async(
+ "roll_call_notification_settings", "call_notification_service"
+ ),
+ get_content_description_async(
+ "roll_call_notification_settings", "call_notification_service"
+ ),
+ self.call_notification_service_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_comment_20_filled"),
+ get_content_name_async(
+ "roll_call_notification_settings", "notification_mode"
+ ),
+ get_content_description_async(
+ "roll_call_notification_settings", "notification_mode"
+ ),
+ self.notification_mode_combo_box,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_sanitize_20_filled"),
+ get_content_name_async("roll_call_notification_settings", "animation"),
+ get_content_description_async(
+ "roll_call_notification_settings", "animation"
+ ),
+ self.animation_switch,
+ )
+
class window_mode(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("roll_call_notification_settings", "window_mode"))
+ self.setTitle(
+ get_content_name_async("roll_call_notification_settings", "window_mode")
+ )
self.setBorderRadius(8)
-
+
# 设置窗口的显示位置下拉框
self.window_position_combo_box = ComboBox()
- self.window_position_combo_box.addItems(get_content_combo_name_async("roll_call_notification_settings", "window_position"))
- self.window_position_combo_box.setCurrentIndex(readme_settings_async("roll_call_notification_settings", "window_position"))
- self.window_position_combo_box.currentIndexChanged.connect(lambda: update_settings("roll_call_notification_settings", "window_position", self.window_position_combo_box.currentIndex()))
+ self.window_position_combo_box.addItems(
+ get_content_combo_name_async(
+ "roll_call_notification_settings", "window_position"
+ )
+ )
+ self.window_position_combo_box.setCurrentIndex(
+ readme_settings_async("roll_call_notification_settings", "window_position")
+ )
+ self.window_position_combo_box.currentIndexChanged.connect(
+ lambda: update_settings(
+ "roll_call_notification_settings",
+ "window_position",
+ self.window_position_combo_box.currentIndex(),
+ )
+ )
# 水平偏移值
self.horizontal_offset_spin_spinbox = SpinBox()
self.horizontal_offset_spin_spinbox.setFixedWidth(WIDTH_SPINBOX)
self.horizontal_offset_spin_spinbox.setRange(-25600, 25600)
self.horizontal_offset_spin_spinbox.setSuffix("px")
- self.horizontal_offset_spin_spinbox.setValue(readme_settings_async("roll_call_notification_settings", "horizontal_offset"))
- self.horizontal_offset_spin_spinbox.valueChanged.connect(lambda: update_settings("roll_call_notification_settings", "horizontal_offset", self.horizontal_offset_spin_spinbox.value()))
+ self.horizontal_offset_spin_spinbox.setValue(
+ readme_settings_async(
+ "roll_call_notification_settings", "horizontal_offset"
+ )
+ )
+ self.horizontal_offset_spin_spinbox.valueChanged.connect(
+ lambda: update_settings(
+ "roll_call_notification_settings",
+ "horizontal_offset",
+ self.horizontal_offset_spin_spinbox.value(),
+ )
+ )
# 垂直偏移值
self.vertical_offset_spin_spinbox = SpinBox()
self.vertical_offset_spin_spinbox.setFixedWidth(WIDTH_SPINBOX)
self.vertical_offset_spin_spinbox.setRange(-25600, 25600)
self.vertical_offset_spin_spinbox.setSuffix("px")
- self.vertical_offset_spin_spinbox.setValue(readme_settings_async("roll_call_notification_settings", "vertical_offset"))
- self.vertical_offset_spin_spinbox.valueChanged.connect(lambda: update_settings("roll_call_notification_settings", "vertical_offset", self.vertical_offset_spin_spinbox.value()))
+ self.vertical_offset_spin_spinbox.setValue(
+ readme_settings_async("roll_call_notification_settings", "vertical_offset")
+ )
+ self.vertical_offset_spin_spinbox.valueChanged.connect(
+ lambda: update_settings(
+ "roll_call_notification_settings",
+ "vertical_offset",
+ self.vertical_offset_spin_spinbox.value(),
+ )
+ )
# 窗口透明度
self.window_transparency_spin_spinbox = SpinBox()
self.window_transparency_spin_spinbox.setFixedWidth(WIDTH_SPINBOX)
self.window_transparency_spin_spinbox.setRange(0, 100)
self.window_transparency_spin_spinbox.setSuffix("%")
- self.window_transparency_spin_spinbox.setValue(readme_settings_async("roll_call_notification_settings", "transparency") * 100)
- self.window_transparency_spin_spinbox.valueChanged.connect(lambda: update_settings("roll_call_notification_settings", "transparency", self.window_transparency_spin_spinbox.value() / 100))
+ self.window_transparency_spin_spinbox.setValue(
+ readme_settings_async("roll_call_notification_settings", "transparency")
+ * 100
+ )
+ self.window_transparency_spin_spinbox.valueChanged.connect(
+ lambda: update_settings(
+ "roll_call_notification_settings",
+ "transparency",
+ self.window_transparency_spin_spinbox.value() / 100,
+ )
+ )
# 选择启用的显示器下拉框
self.enabled_monitor_combo_box = ComboBox()
self.enabled_monitor_combo_box.addItems(self.get_monitor_list())
- if readme_settings_async("roll_call_notification_settings", "enabled_monitor") == "OFF":
+ if (
+ readme_settings_async("roll_call_notification_settings", "enabled_monitor")
+ == "OFF"
+ ):
self.enabled_monitor_combo_box.setCurrentText(self.get_monitor_list()[0])
- update_settings("roll_call_notification_settings", "enabled_monitor", self.enabled_monitor_combo_box.currentText())
- self.enabled_monitor_combo_box.setCurrentText(readme_settings_async("roll_call_notification_settings", "enabled_monitor"))
- self.enabled_monitor_combo_box.currentTextChanged.connect(lambda: self.on_first_monitor_changed(self.enabled_monitor_combo_box.currentText()))
+ update_settings(
+ "roll_call_notification_settings",
+ "enabled_monitor",
+ self.enabled_monitor_combo_box.currentText(),
+ )
+ self.enabled_monitor_combo_box.setCurrentText(
+ readme_settings_async("roll_call_notification_settings", "enabled_monitor")
+ )
+ self.enabled_monitor_combo_box.currentTextChanged.connect(
+ lambda: self.on_first_monitor_changed(
+ self.enabled_monitor_combo_box.currentText()
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_window_text_20_filled"),
- get_content_name_async("roll_call_notification_settings", "enabled_monitor"), get_content_description_async("roll_call_notification_settings", "enabled_monitor"), self.enabled_monitor_combo_box)
- self.addGroup(get_theme_icon("ic_fluent_position_to_back_20_filled"),
- get_content_name_async("roll_call_notification_settings", "window_position"), get_content_description_async("roll_call_notification_settings", "window_position"), self.window_position_combo_box)
- self.addGroup(get_theme_icon("ic_fluent_align_stretch_horizontal_20_filled"),
- get_content_name_async("roll_call_notification_settings", "horizontal_offset"), get_content_description_async("roll_call_notification_settings", "horizontal_offset"), self.horizontal_offset_spin_spinbox)
- self.addGroup(get_theme_icon("ic_fluent_align_stretch_vertical_20_filled"),
- get_content_name_async("roll_call_notification_settings", "vertical_offset"), get_content_description_async("roll_call_notification_settings", "vertical_offset"), self.vertical_offset_spin_spinbox)
- self.addGroup(get_theme_icon("ic_fluent_transparency_square_20_filled"),
- get_content_name_async("roll_call_notification_settings", "transparency"), get_content_description_async("roll_call_notification_settings", "transparency"), self.window_transparency_spin_spinbox)
+ self.addGroup(
+ get_theme_icon("ic_fluent_window_text_20_filled"),
+ get_content_name_async(
+ "roll_call_notification_settings", "enabled_monitor"
+ ),
+ get_content_description_async(
+ "roll_call_notification_settings", "enabled_monitor"
+ ),
+ self.enabled_monitor_combo_box,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_position_to_back_20_filled"),
+ get_content_name_async(
+ "roll_call_notification_settings", "window_position"
+ ),
+ get_content_description_async(
+ "roll_call_notification_settings", "window_position"
+ ),
+ self.window_position_combo_box,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_align_stretch_horizontal_20_filled"),
+ get_content_name_async(
+ "roll_call_notification_settings", "horizontal_offset"
+ ),
+ get_content_description_async(
+ "roll_call_notification_settings", "horizontal_offset"
+ ),
+ self.horizontal_offset_spin_spinbox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_align_stretch_vertical_20_filled"),
+ get_content_name_async(
+ "roll_call_notification_settings", "vertical_offset"
+ ),
+ get_content_description_async(
+ "roll_call_notification_settings", "vertical_offset"
+ ),
+ self.vertical_offset_spin_spinbox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_transparency_square_20_filled"),
+ get_content_name_async("roll_call_notification_settings", "transparency"),
+ get_content_description_async(
+ "roll_call_notification_settings", "transparency"
+ ),
+ self.window_transparency_spin_spinbox,
+ )
# 获取显示器列表
def get_monitor_list(self):
@@ -141,62 +305,169 @@ def get_monitor_list(self):
monitor_list.append(screen.name())
return monitor_list
+
class floating_window_settings(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("roll_call_notification_settings", "floating_window_mode"))
+ self.setTitle(
+ get_content_name_async(
+ "roll_call_notification_settings", "floating_window_mode"
+ )
+ )
self.setBorderRadius(8)
# 窗口位置
self.window_position_combo_box = ComboBox()
- self.window_position_combo_box.addItems(get_content_combo_name_async("roll_call_notification_settings", "floating_window_position"))
- self.window_position_combo_box.setCurrentIndex(readme_settings_async("roll_call_notification_settings", "floating_window_position"))
- self.window_position_combo_box.currentTextChanged.connect(lambda: update_settings("roll_call_notification_settings", "floating_window_position", self.window_position_combo_box.currentIndex()))
-
+ self.window_position_combo_box.addItems(
+ get_content_combo_name_async(
+ "roll_call_notification_settings", "floating_window_position"
+ )
+ )
+ self.window_position_combo_box.setCurrentIndex(
+ readme_settings_async(
+ "roll_call_notification_settings", "floating_window_position"
+ )
+ )
+ self.window_position_combo_box.currentTextChanged.connect(
+ lambda: update_settings(
+ "roll_call_notification_settings",
+ "floating_window_position",
+ self.window_position_combo_box.currentIndex(),
+ )
+ )
+
# 水平偏移值
self.horizontal_offset_spin_spinbox = SpinBox()
self.horizontal_offset_spin_spinbox.setFixedWidth(WIDTH_SPINBOX)
self.horizontal_offset_spin_spinbox.setRange(-25600, 25600)
self.horizontal_offset_spin_spinbox.setSuffix("px")
- self.horizontal_offset_spin_spinbox.setValue(readme_settings_async("roll_call_notification_settings", "floating_window_horizontal_offset"))
- self.horizontal_offset_spin_spinbox.valueChanged.connect(lambda: update_settings("roll_call_notification_settings", "floating_window_horizontal_offset", self.horizontal_offset_spin_spinbox.value()))
+ self.horizontal_offset_spin_spinbox.setValue(
+ readme_settings_async(
+ "roll_call_notification_settings", "floating_window_horizontal_offset"
+ )
+ )
+ self.horizontal_offset_spin_spinbox.valueChanged.connect(
+ lambda: update_settings(
+ "roll_call_notification_settings",
+ "floating_window_horizontal_offset",
+ self.horizontal_offset_spin_spinbox.value(),
+ )
+ )
# 垂直偏移值
self.vertical_offset_spin_spinbox = SpinBox()
self.vertical_offset_spin_spinbox.setFixedWidth(WIDTH_SPINBOX)
self.vertical_offset_spin_spinbox.setRange(-25600, 25600)
self.vertical_offset_spin_spinbox.setSuffix("px")
- self.vertical_offset_spin_spinbox.setValue(readme_settings_async("roll_call_notification_settings", "floating_window_vertical_offset"))
- self.vertical_offset_spin_spinbox.valueChanged.connect(lambda: update_settings("roll_call_notification_settings", "floating_window_vertical_offset", self.vertical_offset_spin_spinbox.value()))
+ self.vertical_offset_spin_spinbox.setValue(
+ readme_settings_async(
+ "roll_call_notification_settings", "floating_window_vertical_offset"
+ )
+ )
+ self.vertical_offset_spin_spinbox.valueChanged.connect(
+ lambda: update_settings(
+ "roll_call_notification_settings",
+ "floating_window_vertical_offset",
+ self.vertical_offset_spin_spinbox.value(),
+ )
+ )
# 窗口透明度
self.window_transparency_spin_spinbox = SpinBox()
self.window_transparency_spin_spinbox.setFixedWidth(WIDTH_SPINBOX)
self.window_transparency_spin_spinbox.setRange(0, 100)
self.window_transparency_spin_spinbox.setSuffix("%")
- self.window_transparency_spin_spinbox.setValue(readme_settings_async("roll_call_notification_settings", "floating_window_transparency") * 100)
- self.window_transparency_spin_spinbox.valueChanged.connect(lambda: update_settings("roll_call_notification_settings", "floating_window_transparency", self.window_transparency_spin_spinbox.value() / 100))
+ self.window_transparency_spin_spinbox.setValue(
+ readme_settings_async(
+ "roll_call_notification_settings", "floating_window_transparency"
+ )
+ * 100
+ )
+ self.window_transparency_spin_spinbox.valueChanged.connect(
+ lambda: update_settings(
+ "roll_call_notification_settings",
+ "floating_window_transparency",
+ self.window_transparency_spin_spinbox.value() / 100,
+ )
+ )
# 选择启用的显示器下拉框
self.enabled_monitor_combo_box = ComboBox()
self.enabled_monitor_combo_box.addItems(self.get_monitor_list())
- if readme_settings_async("roll_call_notification_settings", "floating_window_enabled_monitor") == "OFF":
+ if (
+ readme_settings_async(
+ "roll_call_notification_settings", "floating_window_enabled_monitor"
+ )
+ == "OFF"
+ ):
self.enabled_monitor_combo_box.setCurrentText(self.get_monitor_list()[0])
- update_settings("roll_call_notification_settings", "floating_window_enabled_monitor", self.enabled_monitor_combo_box.currentText())
- self.enabled_monitor_combo_box.setCurrentText(readme_settings_async("roll_call_notification_settings", "floating_window_enabled_monitor"))
- self.enabled_monitor_combo_box.currentTextChanged.connect(lambda: self.on_floating_first_monitor_changed(self.enabled_monitor_combo_box.currentText()))
-
+ update_settings(
+ "roll_call_notification_settings",
+ "floating_window_enabled_monitor",
+ self.enabled_monitor_combo_box.currentText(),
+ )
+ self.enabled_monitor_combo_box.setCurrentText(
+ readme_settings_async(
+ "roll_call_notification_settings", "floating_window_enabled_monitor"
+ )
+ )
+ self.enabled_monitor_combo_box.currentTextChanged.connect(
+ lambda: self.on_floating_first_monitor_changed(
+ self.enabled_monitor_combo_box.currentText()
+ )
+ )
+
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_window_text_20_filled"),
- get_content_name_async("roll_call_notification_settings", "floating_window_enabled_monitor"), get_content_description_async("roll_call_notification_settings", "floating_window_enabled_monitor"), self.enabled_monitor_combo_box)
- self.addGroup(get_theme_icon("ic_fluent_position_to_back_20_filled"),
- get_content_name_async("roll_call_notification_settings", "floating_window_position"), get_content_description_async("roll_call_notification_settings", "floating_window_position"), self.window_position_combo_box)
- self.addGroup(get_theme_icon("ic_fluent_align_stretch_horizontal_20_filled"),
- get_content_name_async("roll_call_notification_settings", "floating_window_horizontal_offset"), get_content_description_async("roll_call_notification_settings", "floating_window_horizontal_offset"), self.horizontal_offset_spin_spinbox)
- self.addGroup(get_theme_icon("ic_fluent_align_stretch_vertical_20_filled"),
- get_content_name_async("roll_call_notification_settings", "floating_window_vertical_offset"), get_content_description_async("roll_call_notification_settings", "floating_window_vertical_offset"), self.vertical_offset_spin_spinbox)
- self.addGroup(get_theme_icon("ic_fluent_transparency_square_20_filled"),
- get_content_name_async("roll_call_notification_settings", "floating_window_transparency"), get_content_description_async("roll_call_notification_settings", "floating_window_transparency"), self.window_transparency_spin_spinbox)
+ self.addGroup(
+ get_theme_icon("ic_fluent_window_text_20_filled"),
+ get_content_name_async(
+ "roll_call_notification_settings", "floating_window_enabled_monitor"
+ ),
+ get_content_description_async(
+ "roll_call_notification_settings", "floating_window_enabled_monitor"
+ ),
+ self.enabled_monitor_combo_box,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_position_to_back_20_filled"),
+ get_content_name_async(
+ "roll_call_notification_settings", "floating_window_position"
+ ),
+ get_content_description_async(
+ "roll_call_notification_settings", "floating_window_position"
+ ),
+ self.window_position_combo_box,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_align_stretch_horizontal_20_filled"),
+ get_content_name_async(
+ "roll_call_notification_settings", "floating_window_horizontal_offset"
+ ),
+ get_content_description_async(
+ "roll_call_notification_settings", "floating_window_horizontal_offset"
+ ),
+ self.horizontal_offset_spin_spinbox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_align_stretch_vertical_20_filled"),
+ get_content_name_async(
+ "roll_call_notification_settings", "floating_window_vertical_offset"
+ ),
+ get_content_description_async(
+ "roll_call_notification_settings", "floating_window_vertical_offset"
+ ),
+ self.vertical_offset_spin_spinbox,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_transparency_square_20_filled"),
+ get_content_name_async(
+ "roll_call_notification_settings", "floating_window_transparency"
+ ),
+ get_content_description_async(
+ "roll_call_notification_settings", "floating_window_transparency"
+ ),
+ self.window_transparency_spin_spinbox,
+ )
# 获取显示器列表
def get_monitor_list(self):
diff --git a/app/view/settings/safety_settings/__init__.py b/app/view/settings/safety_settings/__init__.py
new file mode 100644
index 00000000..8debcbe4
--- /dev/null
+++ b/app/view/settings/safety_settings/__init__.py
@@ -0,0 +1 @@
+"""Safety settings pages."""
diff --git a/app/view/settings/safety_settings/advanced_safety_settings.py b/app/view/settings/safety_settings/advanced_safety_settings.py
index bdb712f2..c46bd92a 100644
--- a/app/view/settings/safety_settings/advanced_safety_settings.py
+++ b/app/view/settings/safety_settings/advanced_safety_settings.py
@@ -1,16 +1,11 @@
# ==================================================
# 导入库
# ==================================================
-import json
-import os
-import sys
-import subprocess
-
-from loguru import logger
-from PyQt6.QtWidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
+
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from PySide6.QtNetwork import *
from qfluentwidgets import *
from app.tools.variable import *
@@ -20,6 +15,7 @@
from app.tools.settings_access import *
from app.Language.obtain_language import *
+
# ==================================================
# 高级安全设置
# ==================================================
@@ -43,59 +39,184 @@ def __init__(self, parent=None):
class advanced_safety_strong_protection(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("advanced_safety_settings", "strong_protection"))
+ self.setTitle(
+ get_content_name_async("advanced_safety_settings", "strong_protection")
+ )
self.setBorderRadius(8)
# 是否启动软件自我保护功能开关
self.encryption_strong_switch = SwitchButton()
- self.encryption_strong_switch.setOffText(get_content_switchbutton_name_async("advanced_safety_settings", "encryption_strong_switch", "disable"))
- self.encryption_strong_switch.setOnText(get_content_switchbutton_name_async("advanced_safety_settings", "encryption_strong_switch", "enable"))
- self.encryption_strong_switch.setChecked(readme_settings_async("advanced_safety_settings", "encryption_strong_switch"))
- self.encryption_strong_switch.checkedChanged.connect(lambda: update_settings("advanced_safety_settings", "encryption_strong_switch", self.encryption_strong_switch.isChecked()))
+ self.encryption_strong_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "advanced_safety_settings", "encryption_strong_switch", "disable"
+ )
+ )
+ self.encryption_strong_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "advanced_safety_settings", "encryption_strong_switch", "enable"
+ )
+ )
+ self.encryption_strong_switch.setChecked(
+ readme_settings_async(
+ "advanced_safety_settings", "encryption_strong_switch"
+ )
+ )
+ self.encryption_strong_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "advanced_safety_settings",
+ "encryption_strong_switch",
+ self.encryption_strong_switch.isChecked(),
+ )
+ )
# 选择强力防删除程度模式
self.encryption_strong_mode_combo = ComboBox()
- self.encryption_strong_mode_combo.addItems(get_content_combo_name_async("advanced_safety_settings", "encryption_strong_mode"))
- self.encryption_strong_mode_combo.setCurrentIndex(readme_settings_async("advanced_safety_settings", "encryption_strong_mode"))
- self.encryption_strong_mode_combo.currentIndexChanged.connect(lambda: update_settings("advanced_safety_settings", "encryption_strong_mode", self.encryption_strong_mode_combo.currentIndex()))
+ self.encryption_strong_mode_combo.addItems(
+ get_content_combo_name_async(
+ "advanced_safety_settings", "encryption_strong_mode"
+ )
+ )
+ self.encryption_strong_mode_combo.setCurrentIndex(
+ readme_settings_async("advanced_safety_settings", "encryption_strong_mode")
+ )
+ self.encryption_strong_mode_combo.currentIndexChanged.connect(
+ lambda: update_settings(
+ "advanced_safety_settings",
+ "encryption_strong_mode",
+ self.encryption_strong_mode_combo.currentIndex(),
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_lock_closed_key_20_filled"),
- get_content_name_async("advanced_safety_settings", "encryption_strong_switch"), get_content_description_async("advanced_safety_settings", "encryption_strong_switch"), self.encryption_strong_switch)
- self.addGroup(get_theme_icon("ic_fluent_haptic_strong_20_filled"),
- get_content_name_async("advanced_safety_settings", "encryption_strong_mode"), get_content_description_async("advanced_safety_settings", "encryption_strong_mode"), self.encryption_strong_mode_combo)
+ self.addGroup(
+ get_theme_icon("ic_fluent_lock_closed_key_20_filled"),
+ get_content_name_async(
+ "advanced_safety_settings", "encryption_strong_switch"
+ ),
+ get_content_description_async(
+ "advanced_safety_settings", "encryption_strong_switch"
+ ),
+ self.encryption_strong_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_haptic_strong_20_filled"),
+ get_content_name_async(
+ "advanced_safety_settings", "encryption_strong_mode"
+ ),
+ get_content_description_async(
+ "advanced_safety_settings", "encryption_strong_mode"
+ ),
+ self.encryption_strong_mode_combo,
+ )
+
class advanced_safety_data_encryption(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("advanced_safety_settings", "data_encryption"))
+ self.setTitle(
+ get_content_name_async("advanced_safety_settings", "data_encryption")
+ )
self.setBorderRadius(8)
# 是否开启名单加密开关
self.encryption_list_switch = SwitchButton()
- self.encryption_list_switch.setOffText(get_content_switchbutton_name_async("advanced_safety_settings", "encryption_list_switch", "disable"))
- self.encryption_list_switch.setOnText(get_content_switchbutton_name_async("advanced_safety_settings", "encryption_list_switch", "enable"))
- self.encryption_list_switch.setChecked(readme_settings_async("advanced_safety_settings", "encryption_list_switch"))
- self.encryption_list_switch.checkedChanged.connect(lambda: update_settings("advanced_safety_settings", "encryption_list_switch", self.encryption_list_switch.isChecked()))
+ self.encryption_list_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "advanced_safety_settings", "encryption_list_switch", "disable"
+ )
+ )
+ self.encryption_list_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "advanced_safety_settings", "encryption_list_switch", "enable"
+ )
+ )
+ self.encryption_list_switch.setChecked(
+ readme_settings_async("advanced_safety_settings", "encryption_list_switch")
+ )
+ self.encryption_list_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "advanced_safety_settings",
+ "encryption_list_switch",
+ self.encryption_list_switch.isChecked(),
+ )
+ )
# 是否开启历史记录文件加密开关
self.encryption_history_switch = SwitchButton()
- self.encryption_history_switch.setOffText(get_content_switchbutton_name_async("advanced_safety_settings", "encryption_history_switch", "disable"))
- self.encryption_history_switch.setOnText(get_content_switchbutton_name_async("advanced_safety_settings", "encryption_history_switch", "enable"))
- self.encryption_history_switch.setChecked(readme_settings_async("advanced_safety_settings", "encryption_history_switch"))
- self.encryption_history_switch.checkedChanged.connect(lambda: update_settings("advanced_safety_settings", "encryption_history_switch", self.encryption_history_switch.isChecked()))
+ self.encryption_history_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "advanced_safety_settings", "encryption_history_switch", "disable"
+ )
+ )
+ self.encryption_history_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "advanced_safety_settings", "encryption_history_switch", "enable"
+ )
+ )
+ self.encryption_history_switch.setChecked(
+ readme_settings_async(
+ "advanced_safety_settings", "encryption_history_switch"
+ )
+ )
+ self.encryption_history_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "advanced_safety_settings",
+ "encryption_history_switch",
+ self.encryption_history_switch.isChecked(),
+ )
+ )
# 是否开启临时已抽取记录加密开关
self.encryption_temp_switch = SwitchButton()
- self.encryption_temp_switch.setOffText(get_content_switchbutton_name_async("advanced_safety_settings", "encryption_temp_switch", "disable"))
- self.encryption_temp_switch.setOnText(get_content_switchbutton_name_async("advanced_safety_settings", "encryption_temp_switch", "enable"))
- self.encryption_temp_switch.setChecked(readme_settings_async("advanced_safety_settings", "encryption_temp_switch"))
- self.encryption_temp_switch.checkedChanged.connect(lambda: update_settings("advanced_safety_settings", "encryption_temp_switch", self.encryption_temp_switch.isChecked()))
+ self.encryption_temp_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "advanced_safety_settings", "encryption_temp_switch", "disable"
+ )
+ )
+ self.encryption_temp_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "advanced_safety_settings", "encryption_temp_switch", "enable"
+ )
+ )
+ self.encryption_temp_switch.setChecked(
+ readme_settings_async("advanced_safety_settings", "encryption_temp_switch")
+ )
+ self.encryption_temp_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "advanced_safety_settings",
+ "encryption_temp_switch",
+ self.encryption_temp_switch.isChecked(),
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_document_bullet_list_clock_20_filled"),
- get_content_name_async("advanced_safety_settings", "encryption_list_switch"), get_content_description_async("advanced_safety_settings", "encryption_list_switch"), self.encryption_list_switch)
- self.addGroup(get_theme_icon("ic_fluent_document_data_lock_20_filled"),
- get_content_name_async("advanced_safety_settings", "encryption_history_switch"), get_content_description_async("advanced_safety_settings", "encryption_history_switch"), self.encryption_history_switch)
- self.addGroup(get_theme_icon("ic_fluent_document_lock_20_filled"),
- get_content_name_async("advanced_safety_settings", "encryption_temp_switch"), get_content_description_async("advanced_safety_settings", "encryption_temp_switch"), self.encryption_temp_switch)
+ self.addGroup(
+ get_theme_icon("ic_fluent_document_bullet_list_clock_20_filled"),
+ get_content_name_async(
+ "advanced_safety_settings", "encryption_list_switch"
+ ),
+ get_content_description_async(
+ "advanced_safety_settings", "encryption_list_switch"
+ ),
+ self.encryption_list_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_document_data_lock_20_filled"),
+ get_content_name_async(
+ "advanced_safety_settings", "encryption_history_switch"
+ ),
+ get_content_description_async(
+ "advanced_safety_settings", "encryption_history_switch"
+ ),
+ self.encryption_history_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_document_lock_20_filled"),
+ get_content_name_async(
+ "advanced_safety_settings", "encryption_temp_switch"
+ ),
+ get_content_description_async(
+ "advanced_safety_settings", "encryption_temp_switch"
+ ),
+ self.encryption_temp_switch,
+ )
diff --git a/app/view/settings/safety_settings/basic_safety_settings.py b/app/view/settings/safety_settings/basic_safety_settings.py
index 0bfd47be..70b4f512 100644
--- a/app/view/settings/safety_settings/basic_safety_settings.py
+++ b/app/view/settings/safety_settings/basic_safety_settings.py
@@ -1,16 +1,11 @@
# ==================================================
# 导入库
# ==================================================
-import json
-import os
-import sys
-import subprocess
-
-from loguru import logger
-from PyQt6.QtWidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
+
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from PySide6.QtNetwork import *
from qfluentwidgets import *
from app.tools.variable import *
@@ -20,6 +15,7 @@
from app.tools.settings_access import *
from app.Language.obtain_language import *
+
# ==================================================
# 安全设置
# ==================================================
@@ -47,111 +43,277 @@ def __init__(self, parent=None):
class basic_safety_verification_method(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("basic_safety_settings", "verification_method"))
+ self.setTitle(
+ get_content_name_async("basic_safety_settings", "verification_method")
+ )
self.setBorderRadius(8)
# 是否开启安全功能开关
self.safety_switch = SwitchButton()
- self.safety_switch.setOffText(get_content_switchbutton_name_async("basic_safety_settings", "safety_switch", "disable"))
- self.safety_switch.setOnText(get_content_switchbutton_name_async("basic_safety_settings", "safety_switch", "enable"))
- self.safety_switch.setChecked(readme_settings_async("basic_safety_settings", "safety_switch"))
- self.safety_switch.checkedChanged.connect(lambda: update_settings("basic_safety_settings", "safety_switch", self.safety_switch.isChecked()))
+ self.safety_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "basic_safety_settings", "safety_switch", "disable"
+ )
+ )
+ self.safety_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "basic_safety_settings", "safety_switch", "enable"
+ )
+ )
+ self.safety_switch.setChecked(
+ readme_settings_async("basic_safety_settings", "safety_switch")
+ )
+ self.safety_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "basic_safety_settings", "safety_switch", self.safety_switch.isChecked()
+ )
+ )
# 设置/修改密码按钮
- self.set_password_button = PushButton(get_content_name_async("basic_safety_settings", "set_password"))
+ self.set_password_button = PushButton(
+ get_content_name_async("basic_safety_settings", "set_password")
+ )
self.set_password_button.clicked.connect(lambda: self.set_password())
# 是否启用TOTP开关
self.totp_switch = SwitchButton()
- self.totp_switch.setOffText(get_content_switchbutton_name_async("basic_safety_settings", "totp_switch", "disable"))
- self.totp_switch.setOnText(get_content_switchbutton_name_async("basic_safety_settings", "totp_switch", "enable"))
- self.totp_switch.setChecked(readme_settings_async("basic_safety_settings", "totp_switch"))
- self.totp_switch.checkedChanged.connect(lambda: update_settings("basic_safety_settings", "totp_switch", self.totp_switch.isChecked()))
+ self.totp_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "basic_safety_settings", "totp_switch", "disable"
+ )
+ )
+ self.totp_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "basic_safety_settings", "totp_switch", "enable"
+ )
+ )
+ self.totp_switch.setChecked(
+ readme_settings_async("basic_safety_settings", "totp_switch")
+ )
+ self.totp_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "basic_safety_settings", "totp_switch", self.totp_switch.isChecked()
+ )
+ )
# 设置TOTP按钮
- self.set_totp_button = PushButton(get_content_name_async("basic_safety_settings", "set_totp"))
+ self.set_totp_button = PushButton(
+ get_content_name_async("basic_safety_settings", "set_totp")
+ )
self.set_totp_button.clicked.connect(lambda: self.set_totp())
# 是否启用U盘验证开关
self.usb_switch = SwitchButton()
- self.usb_switch.setOffText(get_content_switchbutton_name_async("basic_safety_settings", "usb_switch", "disable"))
- self.usb_switch.setOnText(get_content_switchbutton_name_async("basic_safety_settings", "usb_switch", "enable"))
- self.usb_switch.setChecked(readme_settings_async("basic_safety_settings", "usb_switch"))
- self.usb_switch.checkedChanged.connect(lambda: update_settings("basic_safety_settings", "usb_switch", self.usb_switch.isChecked()))
+ self.usb_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "basic_safety_settings", "usb_switch", "disable"
+ )
+ )
+ self.usb_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "basic_safety_settings", "usb_switch", "enable"
+ )
+ )
+ self.usb_switch.setChecked(
+ readme_settings_async("basic_safety_settings", "usb_switch")
+ )
+ self.usb_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "basic_safety_settings", "usb_switch", self.usb_switch.isChecked()
+ )
+ )
# 绑定U盘按钮
- self.bind_usb_button = PushButton(get_content_name_async("basic_safety_settings", "bind_usb"))
+ self.bind_usb_button = PushButton(
+ get_content_name_async("basic_safety_settings", "bind_usb")
+ )
self.bind_usb_button.clicked.connect(lambda: self.bind_usb())
# 解绑U盘按钮
- self.unbind_usb_button = PushButton(get_content_name_async("basic_safety_settings", "unbind_usb"))
+ self.unbind_usb_button = PushButton(
+ get_content_name_async("basic_safety_settings", "unbind_usb")
+ )
self.unbind_usb_button.clicked.connect(lambda: self.unbind_usb())
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_shield_keyhole_20_filled"),
- get_content_name_async("basic_safety_settings", "safety_switch"), get_content_description_async("basic_safety_settings", "safety_switch"), self.safety_switch)
- self.addGroup(get_theme_icon("ic_fluent_laptop_shield_20_filled"),
- get_content_name_async("basic_safety_settings", "set_password"), get_content_description_async("basic_safety_settings", "set_password"), self.set_password_button)
- self.addGroup(get_theme_icon("ic_fluent_puzzle_piece_shield_20_filled"),
- get_content_name_async("basic_safety_settings", "totp_switch"), get_content_description_async("basic_safety_settings", "totp_switch"), self.totp_switch)
- self.addGroup(get_theme_icon("ic_fluent_laptop_shield_20_filled"),
- get_content_name_async("basic_safety_settings", "set_totp"), get_content_description_async("basic_safety_settings", "set_totp"), self.set_totp_button)
- self.addGroup(get_theme_icon("ic_fluent_puzzle_piece_shield_20_filled"),
- get_content_name_async("basic_safety_settings", "usb_switch"), get_content_description_async("basic_safety_settings", "usb_switch"), self.usb_switch)
- self.addGroup(get_theme_icon("ic_fluent_usb_stick_20_filled"),
- get_content_name_async("basic_safety_settings", "bind_usb"), get_content_description_async("basic_safety_settings", "bind_usb"), self.bind_usb_button)
- self.addGroup(get_theme_icon("ic_fluent_usb_plug_20_filled"),
- get_content_name_async("basic_safety_settings", "unbind_usb"), get_content_description_async("basic_safety_settings", "unbind_usb"), self.unbind_usb_button)
+ self.addGroup(
+ get_theme_icon("ic_fluent_shield_keyhole_20_filled"),
+ get_content_name_async("basic_safety_settings", "safety_switch"),
+ get_content_description_async("basic_safety_settings", "safety_switch"),
+ self.safety_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_laptop_shield_20_filled"),
+ get_content_name_async("basic_safety_settings", "set_password"),
+ get_content_description_async("basic_safety_settings", "set_password"),
+ self.set_password_button,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_puzzle_piece_shield_20_filled"),
+ get_content_name_async("basic_safety_settings", "totp_switch"),
+ get_content_description_async("basic_safety_settings", "totp_switch"),
+ self.totp_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_laptop_shield_20_filled"),
+ get_content_name_async("basic_safety_settings", "set_totp"),
+ get_content_description_async("basic_safety_settings", "set_totp"),
+ self.set_totp_button,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_puzzle_piece_shield_20_filled"),
+ get_content_name_async("basic_safety_settings", "usb_switch"),
+ get_content_description_async("basic_safety_settings", "usb_switch"),
+ self.usb_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_usb_stick_20_filled"),
+ get_content_name_async("basic_safety_settings", "bind_usb"),
+ get_content_description_async("basic_safety_settings", "bind_usb"),
+ self.bind_usb_button,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_usb_plug_20_filled"),
+ get_content_name_async("basic_safety_settings", "unbind_usb"),
+ get_content_description_async("basic_safety_settings", "unbind_usb"),
+ self.unbind_usb_button,
+ )
class basic_safety_verification_process(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("basic_safety_settings", "verification_process"))
+ self.setTitle(
+ get_content_name_async("basic_safety_settings", "verification_process")
+ )
self.setBorderRadius(8)
# 选择验证流程下拉框
self.verification_process_combo = ComboBox()
- self.verification_process_combo.addItems(get_content_combo_name_async("basic_safety_settings", "verification_process"))
- self.verification_process_combo.setCurrentIndex(readme_settings_async("basic_safety_settings", "verification_process"))
- self.verification_process_combo.currentIndexChanged.connect(lambda: update_settings("basic_safety_settings", "verification_process", self.verification_process_combo.currentIndex()))
+ self.verification_process_combo.addItems(
+ get_content_combo_name_async(
+ "basic_safety_settings", "verification_process"
+ )
+ )
+ self.verification_process_combo.setCurrentIndex(
+ readme_settings_async("basic_safety_settings", "verification_process")
+ )
+ self.verification_process_combo.currentIndexChanged.connect(
+ lambda: update_settings(
+ "basic_safety_settings",
+ "verification_process",
+ self.verification_process_combo.currentIndex(),
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_calendar_shield_20_filled"),
- get_content_name_async("basic_safety_settings", "verification_process"), get_content_description_async("basic_safety_settings", "verification_process"), self.verification_process_combo)
+ self.addGroup(
+ get_theme_icon("ic_fluent_calendar_shield_20_filled"),
+ get_content_name_async("basic_safety_settings", "verification_process"),
+ get_content_description_async(
+ "basic_safety_settings", "verification_process"
+ ),
+ self.verification_process_combo,
+ )
class basic_safety_security_operations(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("basic_safety_settings", "security_operations"))
+ self.setTitle(
+ get_content_name_async("basic_safety_settings", "security_operations")
+ )
self.setBorderRadius(8)
# 显隐浮窗需验证密码开关
self.show_hide_floating_window_switch = SwitchButton()
- self.show_hide_floating_window_switch.setOffText(get_content_switchbutton_name_async("basic_safety_settings", "show_hide_floating_window_switch", "disable"))
- self.show_hide_floating_window_switch.setOnText(get_content_switchbutton_name_async("basic_safety_settings", "show_hide_floating_window_switch", "enable"))
- self.show_hide_floating_window_switch.setChecked(readme_settings_async("basic_safety_settings", "show_hide_floating_window_switch"))
- self.show_hide_floating_window_switch.checkedChanged.connect(lambda: update_settings("basic_safety_settings", "show_hide_floating_window_switch", self.show_hide_floating_window_switch.isChecked()))
+ self.show_hide_floating_window_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "basic_safety_settings", "show_hide_floating_window_switch", "disable"
+ )
+ )
+ self.show_hide_floating_window_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "basic_safety_settings", "show_hide_floating_window_switch", "enable"
+ )
+ )
+ self.show_hide_floating_window_switch.setChecked(
+ readme_settings_async(
+ "basic_safety_settings", "show_hide_floating_window_switch"
+ )
+ )
+ self.show_hide_floating_window_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "basic_safety_settings",
+ "show_hide_floating_window_switch",
+ self.show_hide_floating_window_switch.isChecked(),
+ )
+ )
# 重启软件需验证密码开关
self.restart_switch = SwitchButton()
- self.restart_switch.setOffText(get_content_switchbutton_name_async("basic_safety_settings", "restart_switch", "disable"))
- self.restart_switch.setOnText(get_content_switchbutton_name_async("basic_safety_settings", "restart_switch", "enable"))
- self.restart_switch.setChecked(readme_settings_async("basic_safety_settings", "restart_switch"))
- self.restart_switch.checkedChanged.connect(lambda: update_settings("basic_safety_settings", "restart_switch", self.restart_switch.isChecked()))
+ self.restart_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "basic_safety_settings", "restart_switch", "disable"
+ )
+ )
+ self.restart_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "basic_safety_settings", "restart_switch", "enable"
+ )
+ )
+ self.restart_switch.setChecked(
+ readme_settings_async("basic_safety_settings", "restart_switch")
+ )
+ self.restart_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "basic_safety_settings",
+ "restart_switch",
+ self.restart_switch.isChecked(),
+ )
+ )
# 退出软件需验证密码开关
self.exit_switch = SwitchButton()
- self.exit_switch.setOffText(get_content_switchbutton_name_async("basic_safety_settings", "exit_switch", "disable"))
- self.exit_switch.setOnText(get_content_switchbutton_name_async("basic_safety_settings", "exit_switch", "enable"))
- self.exit_switch.setChecked(readme_settings_async("basic_safety_settings", "exit_switch"))
- self.exit_switch.checkedChanged.connect(lambda: update_settings("basic_safety_settings", "exit_switch", self.exit_switch.isChecked()))
+ self.exit_switch.setOffText(
+ get_content_switchbutton_name_async(
+ "basic_safety_settings", "exit_switch", "disable"
+ )
+ )
+ self.exit_switch.setOnText(
+ get_content_switchbutton_name_async(
+ "basic_safety_settings", "exit_switch", "enable"
+ )
+ )
+ self.exit_switch.setChecked(
+ readme_settings_async("basic_safety_settings", "exit_switch")
+ )
+ self.exit_switch.checkedChanged.connect(
+ lambda: update_settings(
+ "basic_safety_settings", "exit_switch", self.exit_switch.isChecked()
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_window_ad_20_filled"),
- get_content_name_async("basic_safety_settings", "show_hide_floating_window_switch"), get_content_description_async("basic_safety_settings", "show_hide_floating_window_switch"), self.show_hide_floating_window_switch)
- self.addGroup(get_theme_icon("ic_fluent_arrow_reset_20_filled"),
- get_content_name_async("basic_safety_settings", "restart_switch"), get_content_description_async("basic_safety_settings", "restart_switch"), self.restart_switch)
- self.addGroup(get_theme_icon("ic_fluent_arrow_exit_20_filled"),
- get_content_name_async("basic_safety_settings", "exit_switch"), get_content_description_async("basic_safety_settings", "exit_switch"), self.exit_switch)
+ self.addGroup(
+ get_theme_icon("ic_fluent_window_ad_20_filled"),
+ get_content_name_async(
+ "basic_safety_settings", "show_hide_floating_window_switch"
+ ),
+ get_content_description_async(
+ "basic_safety_settings", "show_hide_floating_window_switch"
+ ),
+ self.show_hide_floating_window_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_arrow_reset_20_filled"),
+ get_content_name_async("basic_safety_settings", "restart_switch"),
+ get_content_description_async("basic_safety_settings", "restart_switch"),
+ self.restart_switch,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_arrow_exit_20_filled"),
+ get_content_name_async("basic_safety_settings", "exit_switch"),
+ get_content_description_async("basic_safety_settings", "exit_switch"),
+ self.exit_switch,
+ )
diff --git a/app/view/settings/settings.py b/app/view/settings/settings.py
index e5b735bd..e82c5f13 100644
--- a/app/view/settings/settings.py
+++ b/app/view/settings/settings.py
@@ -1,17 +1,23 @@
# ==================================================
# 导入库
# ==================================================
-import json
-import os
-import sys
-import subprocess
from loguru import logger
-from PyQt6.QtWidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
-from qfluentwidgets import *
+from PySide6.QtWidgets import QApplication
+from PySide6.QtGui import QIcon
+from PySide6.QtCore import QTimer, QEvent, Signal
+from qfluentwidgets import MSFluentWindow, NavigationItemPosition
+
+from app.tools.variable import (
+ MINIMUM_WINDOW_SIZE,
+ APP_INIT_DELAY,
+ SETTINGS_WARMUP_INTERVAL_MS,
+ SETTINGS_WARMUP_MAX_PRELOAD,
+)
+from app.tools.path_utils import get_resources_path
+from app.tools.personalised import get_theme_icon
+from app.Language.obtain_language import get_content_name_async
+from app.tools.settings_access import readme_settings_async, update_settings
from app.tools.variable import *
from app.tools.path_utils import *
@@ -20,9 +26,6 @@
from app.tools.settings_access import *
from app.Language.obtain_language import *
-from app.tools.extract import _is_non_class_time
-
-from app.page_building.settings_window_page import *
# ==================================================
# 主窗口类
@@ -30,40 +33,67 @@
class SettingsWindow(MSFluentWindow):
"""主窗口类
程序的核心控制中心"""
- showSettingsRequested = pyqtSignal()
- showSettingsRequestedAbout = pyqtSignal()
-
+
+ showSettingsRequested = Signal()
+ showSettingsRequestedAbout = Signal()
+
def __init__(self, parent=None):
super().__init__()
self.setObjectName("settingWindow")
self.parent = parent
+ # 初始化变量
+ self.homeInterface = None
+ self.basicSettingsInterface = None
+ self.listManagementInterface = None
+ self.extractionSettingsInterface = None
+ self.notificationSettingsInterface = None
+ self.safetySettingsInterface = None
+ self.customSettingsInterface = None
+ self.voiceSettingsInterface = None
+ self.historyInterface = None
+ self.moreSettingsInterface = None
+ self.aboutInterface = None
+
# resize_timer的初始化
self.resize_timer = QTimer(self)
self.resize_timer.setSingleShot(True)
- self.resize_timer.timeout.connect(lambda: self.save_window_size(self.width(), self.height()))
+ self.resize_timer.timeout.connect(
+ lambda: self.save_window_size(self.width(), self.height())
+ )
# 设置窗口属性
window_width = 800
window_height = 600
self.resize(window_width, window_height)
self.setMinimumSize(MINIMUM_WINDOW_SIZE[0], MINIMUM_WINDOW_SIZE[1])
- self.setWindowTitle('SecRandom')
- self.setWindowIcon(QIcon(str(get_resources_path('assets/icon', 'secrandom-icon-paper.png'))))
+ self.setWindowTitle("SecRandom")
+ self.setWindowIcon(
+ QIcon(str(get_resources_path("assets/icon", "secrandom-icon-paper.png")))
+ )
+ # 窗口定位
self._position_window()
-
- QTimer.singleShot(APP_INIT_DELAY, lambda: (
- self.createSubInterface()
- ))
+
+ # 启动页面
+ self.splashScreen = SplashScreen(self.windowIcon(), self)
+ self.splashScreen.setIconSize(QSize(256, 256))
+ self.show()
+
+ # 初始化子界面
+ QTimer.singleShot(APP_INIT_DELAY, lambda: (self.createSubInterface()))
def _position_window(self):
"""窗口定位
根据屏幕尺寸和用户设置自动计算最佳位置"""
is_maximized = readme_settings_async("settings", "is_maximized")
if is_maximized:
- pre_maximized_width = readme_settings_async("settings", "pre_maximized_width")
- pre_maximized_height = readme_settings_async("settings", "pre_maximized_height")
+ pre_maximized_width = readme_settings_async(
+ "settings", "pre_maximized_width"
+ )
+ pre_maximized_height = readme_settings_async(
+ "settings", "pre_maximized_height"
+ )
self.resize(pre_maximized_width, pre_maximized_height)
self._center_window()
QTimer.singleShot(100, self.showMaximized)
@@ -82,7 +112,7 @@ def _center_window(self):
target_x = w // 2 - self.width() // 2
target_y = h // 2 - self.height() // 2
-
+
self.move(target_x, target_y)
def _apply_window_visibility_settings(self):
@@ -96,65 +126,281 @@ def _apply_window_visibility_settings(self):
def createSubInterface(self):
"""创建子界面
搭建子界面导航系统"""
- self.homeInterface = home_page(self)
- self.homeInterface.setObjectName("homeInterface")
-
- self.basicSettingsInterface = basic_settings_page(self)
- self.basicSettingsInterface.setObjectName("basicSettingsInterface")
-
- self.listManagementInterface = list_management_page(self)
- self.listManagementInterface.setObjectName("listManagementInterface")
-
- self.extractionSettingsInterface = extraction_settings_page(self)
- self.extractionSettingsInterface.setObjectName("extractionSettingsInterface")
-
- self.notificationSettingsInterface = notification_settings_page(self)
- self.notificationSettingsInterface.setObjectName("notificationSettingsInterface")
-
- self.safetySettingsInterface = safety_settings_page(self)
- self.safetySettingsInterface.setObjectName("safetySettingsInterface")
-
- self.customSettingsInterface = custom_settings_page(self)
- self.customSettingsInterface.setObjectName("customSettingsInterface")
-
- self.voiceSettingsInterface = voice_settings_page(self)
- self.voiceSettingsInterface.setObjectName("voiceSettingsInterface")
+ # 延迟创建页面:先创建轻量占位容器并注册工厂
+ from app.page_building import settings_window_page
+
+ # 存储占位 -> factory 映射
+ self._deferred_factories = {}
+
+ def make_placeholder(name: str):
+ w = QWidget()
+ w.setObjectName(name)
+ # 使用空布局以便后续将真正页面加入
+ layout = QVBoxLayout(w)
+ layout.setContentsMargins(0, 0, 0, 0)
+ return w
+
+ self.homeInterface = make_placeholder("homeInterface")
+ self._deferred_factories["homeInterface"] = (
+ lambda parent=self.homeInterface: settings_window_page.home_page(parent)
+ )
+
+ self.basicSettingsInterface = make_placeholder("basicSettingsInterface")
+ self._deferred_factories["basicSettingsInterface"] = (
+ lambda parent=self.basicSettingsInterface: settings_window_page.basic_settings_page(
+ parent
+ )
+ )
+
+ self.listManagementInterface = make_placeholder("listManagementInterface")
+ self._deferred_factories["listManagementInterface"] = (
+ lambda parent=self.listManagementInterface: settings_window_page.list_management_page(
+ parent
+ )
+ )
+
+ self.extractionSettingsInterface = make_placeholder(
+ "extractionSettingsInterface"
+ )
+ self._deferred_factories["extractionSettingsInterface"] = (
+ lambda parent=self.extractionSettingsInterface: settings_window_page.extraction_settings_page(
+ parent
+ )
+ )
+
+ self.notificationSettingsInterface = make_placeholder(
+ "notificationSettingsInterface"
+ )
+ self._deferred_factories["notificationSettingsInterface"] = (
+ lambda parent=self.notificationSettingsInterface: settings_window_page.notification_settings_page(
+ parent
+ )
+ )
+
+ self.safetySettingsInterface = make_placeholder("safetySettingsInterface")
+ self._deferred_factories["safetySettingsInterface"] = (
+ lambda parent=self.safetySettingsInterface: settings_window_page.safety_settings_page(
+ parent
+ )
+ )
+
+ self.customSettingsInterface = make_placeholder("customSettingsInterface")
+ self._deferred_factories["customSettingsInterface"] = (
+ lambda parent=self.customSettingsInterface: settings_window_page.custom_settings_page(
+ parent
+ )
+ )
+
+ self.voiceSettingsInterface = make_placeholder("voiceSettingsInterface")
+ self._deferred_factories["voiceSettingsInterface"] = (
+ lambda parent=self.voiceSettingsInterface: settings_window_page.voice_settings_page(
+ parent
+ )
+ )
+
+ self.historyInterface = make_placeholder("historyInterface")
+ self._deferred_factories["historyInterface"] = (
+ lambda parent=self.historyInterface: settings_window_page.history_page(
+ parent
+ )
+ )
+
+ self.moreSettingsInterface = make_placeholder("moreSettingsInterface")
+ self._deferred_factories["moreSettingsInterface"] = (
+ lambda parent=self.moreSettingsInterface: settings_window_page.more_settings_page(
+ parent
+ )
+ )
+
+ self.aboutInterface = make_placeholder("aboutInterface")
+ self._deferred_factories["aboutInterface"] = (
+ lambda parent=self.aboutInterface: settings_window_page.about_page(parent)
+ )
+
+ # 把占位注册到导航,但不要在此刻实例化真实页面
+ self.initNavigation()
- self.historyInterface = history_page(self)
- self.historyInterface.setObjectName("historyInterface")
+ # 连接堆叠窗口切换信号,在首次切换到占位时创建真实页面
+ try:
+ self.stackedWidget.currentChanged.connect(self._on_stacked_widget_changed)
+ except Exception:
+ pass
- self.moreSettingsInterface = more_settings_page(self)
- self.moreSettingsInterface.setObjectName("moreSettingsInterface")
+ # 在窗口显示后启动后台预热,分批创建其余页面,避免一次性阻塞
+ try:
+ QTimer.singleShot(300, lambda: self._background_warmup_pages())
+ except Exception:
+ pass
- self.aboutInterface = about_page(self)
- self.aboutInterface.setObjectName("aboutInterface")
+ def _on_stacked_widget_changed(self, index: int):
+ """当导航切换到某个占位页时,按需创建真实页面内容"""
+ try:
+ widget = self.stackedWidget.widget(index)
+ if not widget:
+ return
+ name = widget.objectName()
+ # 如果有延迟工厂且容器尚未填充内容,则创建真实页面
+ if (
+ name in getattr(self, "_deferred_factories", {})
+ and widget.layout()
+ and widget.layout().count() == 0
+ ):
+ factory = self._deferred_factories.pop(name)
+ try:
+ real_page = factory()
+ # real_page 会在其内部创建内容(PageTemplate 会在事件循环中再创建内部内容),
+ # 我们把它作为子控件加入占位容器
+ widget.layout().addWidget(real_page)
+ logger.debug(f"设置页面已按需创建: {name}")
+ except Exception as e:
+ logger.error(f"延迟创建设置页面 {name} 失败: {e}")
+ except Exception as e:
+ logger.error(f"处理堆叠窗口改变失败: {e}")
+
+ def _background_warmup_pages(
+ self,
+ interval_ms: int = SETTINGS_WARMUP_INTERVAL_MS,
+ max_preload: int = SETTINGS_WARMUP_MAX_PRELOAD,
+ ):
+ """分批(间隔)创建剩余的设置页面,减少单次阻塞。
+
+ 参数:
+ interval_ms: 每个页面创建间隔(毫秒)
+ """
+ try:
+ # 复制键避免在迭代时修改字典
+ names = list(getattr(self, "_deferred_factories", {}).keys())
+ if not names:
+ return
+ # 仅预热有限数量的页面,避免一次性占用主线程
+ names_to_preload = names[:max_preload]
+ logger.debug(f"后台预热将创建 {len(names_to_preload)} / {len(names)} 个页面")
+ # 仅为要预热的页面调度创建,避免一次性调度所有页面
+ for i, name in enumerate(names_to_preload):
+ # 延迟创建,避免短时间内占用主线程
+ QTimer.singleShot(
+ interval_ms * i,
+ (lambda n=name: self._create_deferred_page(n)),
+ )
+ except Exception as e:
+ logger.error(f"后台预热设置页面失败: {e}")
- self.initNavigation()
+ def _create_deferred_page(self, name: str):
+ """根据名字创建对应延迟工厂并把结果加入占位容器"""
+ try:
+ if name not in getattr(self, "_deferred_factories", {}):
+ return
+ factory = self._deferred_factories.pop(name)
+ # 找到对应占位容器
+ container = None
+ for w in [
+ self.homeInterface,
+ self.basicSettingsInterface,
+ self.listManagementInterface,
+ self.extractionSettingsInterface,
+ self.notificationSettingsInterface,
+ self.safetySettingsInterface,
+ self.customSettingsInterface,
+ self.voiceSettingsInterface,
+ self.historyInterface,
+ self.moreSettingsInterface,
+ self.aboutInterface,
+ ]:
+ if w and w.objectName() == name:
+ container = w
+ break
+ if container is None:
+ return
+ try:
+ real_page = factory()
+ container.layout().addWidget(real_page)
+ logger.debug(f"后台预热创建设置页面: {name}")
+ except Exception as e:
+ logger.error(f"创建延迟页面 {name} 失败: {e}")
+ except Exception as e:
+ logger.error(f"_create_deferred_page 失败: {e}")
def initNavigation(self):
"""初始化导航系统
根据用户设置构建个性化菜单导航"""
- self.addSubInterface(self.homeInterface, get_theme_icon("ic_fluent_home_20_filled"), get_content_name_async("home", "title"), position=NavigationItemPosition.TOP)
-
- self.addSubInterface(self.basicSettingsInterface, get_theme_icon("ic_fluent_wrench_settings_20_filled"), get_content_name_async("basic_settings", "title"), position=NavigationItemPosition.TOP)
-
- self.addSubInterface(self.listManagementInterface, get_theme_icon("ic_fluent_list_20_filled"), get_content_name_async("list_management", "title"), position=NavigationItemPosition.TOP)
-
- self.addSubInterface(self.extractionSettingsInterface, get_theme_icon("ic_fluent_archive_20_filled"), get_content_name_async("extraction_settings", "title"), position=NavigationItemPosition.TOP)
-
- self.addSubInterface(self.notificationSettingsInterface, get_theme_icon("ic_fluent_comment_note_20_filled"), get_content_name_async("notification_settings", "title"), position=NavigationItemPosition.TOP)
-
- self.addSubInterface(self.safetySettingsInterface, get_theme_icon("ic_fluent_shield_20_filled"), get_content_name_async("safety_settings", "title"), position=NavigationItemPosition.TOP)
-
- self.addSubInterface(self.customSettingsInterface, get_theme_icon("ic_fluent_person_edit_20_filled"), get_content_name_async("custom_settings", "title"), position=NavigationItemPosition.TOP)
-
- self.addSubInterface(self.voiceSettingsInterface, get_theme_icon("ic_fluent_person_voice_20_filled"), get_content_name_async("voice_settings", "title"), position=NavigationItemPosition.TOP)
-
- self.addSubInterface(self.historyInterface, get_theme_icon("ic_fluent_history_20_filled"), get_content_name_async("history", "title"), position=NavigationItemPosition.TOP)
-
- self.addSubInterface(self.moreSettingsInterface, get_theme_icon("ic_fluent_more_horizontal_20_filled"), get_content_name_async("more_settings", "title"), position=NavigationItemPosition.TOP)
-
- self.addSubInterface(self.aboutInterface, get_theme_icon("ic_fluent_info_20_filled"), get_content_name_async("about", "title"), position=NavigationItemPosition.BOTTOM)
+ self.addSubInterface(
+ self.homeInterface,
+ get_theme_icon("ic_fluent_home_20_filled"),
+ get_content_name_async("home", "title"),
+ position=NavigationItemPosition.TOP,
+ )
+
+ self.addSubInterface(
+ self.basicSettingsInterface,
+ get_theme_icon("ic_fluent_wrench_settings_20_filled"),
+ get_content_name_async("basic_settings", "title"),
+ position=NavigationItemPosition.TOP,
+ )
+
+ self.addSubInterface(
+ self.listManagementInterface,
+ get_theme_icon("ic_fluent_list_20_filled"),
+ get_content_name_async("list_management", "title"),
+ position=NavigationItemPosition.TOP,
+ )
+
+ self.addSubInterface(
+ self.extractionSettingsInterface,
+ get_theme_icon("ic_fluent_archive_20_filled"),
+ get_content_name_async("extraction_settings", "title"),
+ position=NavigationItemPosition.TOP,
+ )
+
+ self.addSubInterface(
+ self.notificationSettingsInterface,
+ get_theme_icon("ic_fluent_comment_note_20_filled"),
+ get_content_name_async("notification_settings", "title"),
+ position=NavigationItemPosition.TOP,
+ )
+
+ self.addSubInterface(
+ self.safetySettingsInterface,
+ get_theme_icon("ic_fluent_shield_20_filled"),
+ get_content_name_async("safety_settings", "title"),
+ position=NavigationItemPosition.TOP,
+ )
+
+ self.addSubInterface(
+ self.customSettingsInterface,
+ get_theme_icon("ic_fluent_person_edit_20_filled"),
+ get_content_name_async("custom_settings", "title"),
+ position=NavigationItemPosition.TOP,
+ )
+
+ self.addSubInterface(
+ self.voiceSettingsInterface,
+ get_theme_icon("ic_fluent_person_voice_20_filled"),
+ get_content_name_async("voice_settings", "title"),
+ position=NavigationItemPosition.TOP,
+ )
+
+ self.addSubInterface(
+ self.historyInterface,
+ get_theme_icon("ic_fluent_history_20_filled"),
+ get_content_name_async("history", "title"),
+ position=NavigationItemPosition.TOP,
+ )
+
+ self.addSubInterface(
+ self.moreSettingsInterface,
+ get_theme_icon("ic_fluent_more_horizontal_20_filled"),
+ get_content_name_async("more_settings", "title"),
+ position=NavigationItemPosition.TOP,
+ )
+
+ self.addSubInterface(
+ self.aboutInterface,
+ get_theme_icon("ic_fluent_info_20_filled"),
+ get_content_name_async("about", "title"),
+ position=NavigationItemPosition.BOTTOM,
+ )
+
+ self.splashScreen.finish()
def closeEvent(self, event):
"""窗口关闭事件处理
@@ -174,7 +420,7 @@ def resizeEvent(self, event):
# 正常的窗口大小变化处理
self.resize_timer.start(500)
super().resizeEvent(event)
-
+
def changeEvent(self, event):
"""窗口状态变化事件处理
检测窗口最大化/恢复状态变化,保存正确的窗口大小"""
@@ -186,13 +432,24 @@ def changeEvent(self, event):
update_settings("settings", "is_maximized", is_currently_maximized)
if is_currently_maximized:
normal_geometry = self.normalGeometry()
- update_settings("settings", "pre_maximized_width", normal_geometry.width())
- update_settings("settings", "pre_maximized_height", normal_geometry.height())
+ update_settings(
+ "settings", "pre_maximized_width", normal_geometry.width()
+ )
+ update_settings(
+ "settings", "pre_maximized_height", normal_geometry.height()
+ )
else:
- pre_maximized_width = readme_settings_async("settings", "pre_maximized_width")
- pre_maximized_height = readme_settings_async("settings", "pre_maximized_height")
- QTimer.singleShot(100, lambda: self.resize(pre_maximized_width, pre_maximized_height))
-
+ pre_maximized_width = readme_settings_async(
+ "settings", "pre_maximized_width"
+ )
+ pre_maximized_height = readme_settings_async(
+ "settings", "pre_maximized_height"
+ )
+ QTimer.singleShot(
+ 100,
+ lambda: self.resize(pre_maximized_width, pre_maximized_height),
+ )
+
super().changeEvent(event)
def save_window_size(self, setting_window_width, setting_window_height):
@@ -224,4 +481,4 @@ def show_settings_window_about(self):
self.activateWindow()
self.raise_()
- self.switchTo(self.aboutInterface)
\ No newline at end of file
+ self.switchTo(self.aboutInterface)
diff --git a/app/view/settings/voice_settings/__init__.py b/app/view/settings/voice_settings/__init__.py
new file mode 100644
index 00000000..053f1fc2
--- /dev/null
+++ b/app/view/settings/voice_settings/__init__.py
@@ -0,0 +1 @@
+"""Voice settings pages."""
diff --git a/app/view/settings/voice_settings/basic_voice_settings.py b/app/view/settings/voice_settings/basic_voice_settings.py
index 03315b53..b65d929f 100644
--- a/app/view/settings/voice_settings/basic_voice_settings.py
+++ b/app/view/settings/voice_settings/basic_voice_settings.py
@@ -1,19 +1,15 @@
# ==================================================
# 导入库
# ==================================================
-import json
-import os
-import sys
-import subprocess
import asyncio
import edge_tts
import aiohttp
from loguru import logger
-from PyQt6.QtWidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from PySide6.QtNetwork import *
from qfluentwidgets import *
from app.tools.variable import *
@@ -23,6 +19,7 @@
from app.tools.settings_access import *
from app.Language.obtain_language import *
+
# ==================================================
# 基本语音设置
# ==================================================
@@ -46,79 +43,122 @@ def __init__(self, parent=None):
self.system_volume_widget = basic_settings_system_volume(self)
self.vBoxLayout.addWidget(self.system_volume_widget)
+
class basic_settings_voice_engine(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("basic_voice_settings", "voice_engine_group"))
+ self.setTitle(
+ get_content_name_async("basic_voice_settings", "voice_engine_group")
+ )
self.setBorderRadius(8)
-
+
# 初始化异步更新相关变量
self._is_updating_voices = False
# 语音引擎设置
self.voice_engine = ComboBox()
- self.voice_engine.addItems(get_content_combo_name_async("basic_voice_settings", "voice_engine"))
- self.voice_engine.setCurrentText(readme_settings_async("basic_voice_settings", "voice_engine"))
+ self.voice_engine.addItems(
+ get_content_combo_name_async("basic_voice_settings", "voice_engine")
+ )
+ self.voice_engine.setCurrentText(
+ readme_settings_async("basic_voice_settings", "voice_engine")
+ )
self.voice_engine.currentTextChanged.connect(self.on_voice_engine_changed)
-
+
# 初始化Edge TTS语音名称设置
self.edge_tts_voice_name = ComboBox()
- self.edge_tts_voice_name.addItems(get_content_combo_name_async("basic_voice_settings", "edge_tts_voice_name"))
- self.edge_tts_voice_name.setCurrentText(readme_settings_async("basic_voice_settings", "edge_tts_voice_name"))
- self.edge_tts_voice_name.currentTextChanged.connect(lambda text: update_settings("basic_voice_settings", "edge_tts_voice_name", text))
-
+ self.edge_tts_voice_name.addItems(
+ get_content_combo_name_async("basic_voice_settings", "edge_tts_voice_name")
+ )
+ self.edge_tts_voice_name.setCurrentText(
+ readme_settings_async("basic_voice_settings", "edge_tts_voice_name")
+ )
+ self.edge_tts_voice_name.currentTextChanged.connect(
+ lambda text: update_settings(
+ "basic_voice_settings", "edge_tts_voice_name", text
+ )
+ )
+
# 根据当前语音引擎设置Edge TTS语音名称的可用性
current_engine = self.voice_engine.currentText()
self.edge_tts_voice_name.setEnabled(current_engine == "Edge TTS")
-
+
# 如果当前是Edge TTS,异步更新语音列表
if current_engine == "Edge TTS":
QTimer.singleShot(1000, self._async_update_edge_tts_voices)
# 语音播放设备设置
self.voice_playback = ComboBox()
- self.voice_playback.addItems(get_content_combo_name_async("basic_voice_settings", "voice_playback"))
- self.voice_playback.setCurrentText(readme_settings_async("basic_voice_settings", "voice_playback"))
- self.voice_playback.currentTextChanged.connect(lambda text: update_settings("basic_voice_settings", "voice_playback", text))
+ self.voice_playback.addItems(
+ get_content_combo_name_async("basic_voice_settings", "voice_playback")
+ )
+ self.voice_playback.setCurrentText(
+ readme_settings_async("basic_voice_settings", "voice_playback")
+ )
+ self.voice_playback.currentTextChanged.connect(
+ lambda text: update_settings("basic_voice_settings", "voice_playback", text)
+ )
# 语速调节设置
self.speech_rate = SpinBox()
self.speech_rate.setFixedWidth(WIDTH_SPINBOX)
self.speech_rate.setRange(0, 100)
self.speech_rate.setSuffix("wpm")
- self.speech_rate.setValue(int(readme_settings_async("basic_voice_settings", "speech_rate")))
- self.speech_rate.valueChanged.connect(lambda value: update_settings("basic_voice_settings", "speech_rate", value))
+ self.speech_rate.setValue(
+ int(readme_settings_async("basic_voice_settings", "speech_rate"))
+ )
+ self.speech_rate.valueChanged.connect(
+ lambda value: update_settings("basic_voice_settings", "speech_rate", value)
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_speaker_2_20_filled"),
- get_content_name_async("basic_voice_settings", "voice_engine"), get_content_description_async("basic_voice_settings", "voice_engine"), self.voice_engine)
- self.addGroup(get_theme_icon("ic_fluent_mic_record_20_filled"),
- get_content_name_async("basic_voice_settings", "edge_tts_voice_name"), get_content_description_async("basic_voice_settings", "edge_tts_voice_name"), self.edge_tts_voice_name)
- self.addGroup(get_theme_icon("ic_fluent_speaker_0_20_filled"),
- get_content_name_async("basic_voice_settings", "voice_playback"), get_content_description_async("basic_voice_settings", "voice_playback"), self.voice_playback)
- self.addGroup(get_theme_icon("ic_fluent_top_speed_20_filled"),
- get_content_name_async("basic_voice_settings", "speech_rate"), get_content_description_async("basic_voice_settings", "speech_rate"), self.speech_rate)
-
+ self.addGroup(
+ get_theme_icon("ic_fluent_speaker_2_20_filled"),
+ get_content_name_async("basic_voice_settings", "voice_engine"),
+ get_content_description_async("basic_voice_settings", "voice_engine"),
+ self.voice_engine,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_mic_record_20_filled"),
+ get_content_name_async("basic_voice_settings", "edge_tts_voice_name"),
+ get_content_description_async(
+ "basic_voice_settings", "edge_tts_voice_name"
+ ),
+ self.edge_tts_voice_name,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_speaker_0_20_filled"),
+ get_content_name_async("basic_voice_settings", "voice_playback"),
+ get_content_description_async("basic_voice_settings", "voice_playback"),
+ self.voice_playback,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_top_speed_20_filled"),
+ get_content_name_async("basic_voice_settings", "speech_rate"),
+ get_content_description_async("basic_voice_settings", "speech_rate"),
+ self.speech_rate,
+ )
+
def on_voice_engine_changed(self, text):
"""语音引擎改变时的处理函数"""
# 更新设置
update_settings("basic_voice_settings", "voice_engine", text)
-
+
# 根据选择的引擎启用/禁用Edge TTS语音名称选择
- is_edge_tts = (text == "Edge TTS")
+ is_edge_tts = text == "Edge TTS"
self.edge_tts_voice_name.setEnabled(is_edge_tts)
-
+
# 当切换到Edge TTS时,延迟异步更新语音列表,避免频繁触发网络请求
if is_edge_tts:
# 使用QTimer延迟执行,给UI一些响应时间
QTimer.singleShot(500, self._async_update_edge_tts_voices)
-
+
def _async_update_edge_tts_voices(self):
"""异步更新Edge TTS语音列表"""
# 如果正在更新,则不再重复请求
if self._is_updating_voices:
return
-
+
self._is_updating_voices = True
try:
# 尝试获取当前事件循环
@@ -126,7 +166,12 @@ def _async_update_edge_tts_voices(self):
loop = asyncio.get_event_loop()
if loop.is_running():
# 如果循环正在运行,使用QTimer.singleShot创建任务
- QTimer.singleShot(0, lambda: asyncio.create_task(self._update_edge_tts_voices_task()))
+ QTimer.singleShot(
+ 0,
+ lambda: asyncio.create_task(
+ self._update_edge_tts_voices_task()
+ ),
+ )
else:
# 如果循环没有运行,直接创建任务
loop.create_task(self._update_edge_tts_voices_task())
@@ -138,7 +183,7 @@ def _async_update_edge_tts_voices(self):
except Exception as e:
logger.error(f"创建Edge TTS语音更新任务失败: {e}")
self._is_updating_voices = False
-
+
async def _update_edge_tts_voices_task(self):
"""异步更新Edge TTS语音列表的任务"""
try:
@@ -146,7 +191,7 @@ async def _update_edge_tts_voices_task(self):
voices = await self._get_edge_tts_voices()
# 获取当前选中的语音
current_voice = self.edge_tts_voice_name.currentText()
-
+
# 使用信号槽机制在主线程中更新UI
self._update_voice_combo_box(voices, current_voice)
except Exception as e:
@@ -154,15 +199,15 @@ async def _update_edge_tts_voices_task(self):
finally:
# 无论成功或失败,都要重置更新标志
self._is_updating_voices = False
-
- @pyqtSlot(list, str)
+
+ @Slot(list, str)
def _update_voice_combo_box(self, voices, current_voice):
"""在主线程中更新语音列表下拉框"""
try:
# 清空当前列表
self.edge_tts_voice_name.clear()
# 添加新的语音列表
- self.edge_tts_voice_name.addItems([v['id'] for v in voices])
+ self.edge_tts_voice_name.addItems([v["id"] for v in voices])
# 尝试恢复之前选中的语音
index = self.edge_tts_voice_name.findText(current_voice)
if index >= 0:
@@ -170,59 +215,97 @@ def _update_voice_combo_box(self, voices, current_voice):
else:
# 如果找不到之前选中的语音,使用默认语音
self.edge_tts_voice_name.setCurrentText("zh-CN-XiaoxiaoNeural")
-
+
logger.info(f"Edge TTS语音列表已更新,共{len(voices)}个语音")
except Exception as e:
logger.error(f"更新语音列表下拉框失败: {e}")
-
+
async def _get_edge_tts_voices(self):
"""获取Edge TTS语音列表"""
max_retries = 3
retry_delay = 2 # 秒
-
+
for attempt in range(max_retries):
try:
voices = await edge_tts.list_voices()
- filtered_voices = [{
- "name": v['FriendlyName'],
- "id": v['ShortName'] if not v['Locale'].startswith('zh-CN') else f"zh-CN-{v['FriendlyName'].split()[1]}Neural",
- "languages": v['Locale'].replace('_', '-'),
- "full_info": f"{v['Gender']} | {v['Locale']} | Type: {v.get('VoiceType', 'Unknown')}"
- } for v in voices if v['Locale']]
+ filtered_voices = [
+ {
+ "name": v["FriendlyName"],
+ "id": v["ShortName"]
+ if not v["Locale"].startswith("zh-CN")
+ else f"zh-CN-{v['FriendlyName'].split()[1]}Neural",
+ "languages": v["Locale"].replace("_", "-"),
+ "full_info": f"{v['Gender']} | {v['Locale']} | Type: {v.get('VoiceType', 'Unknown')}",
+ }
+ for v in voices
+ if v["Locale"]
+ ]
return filtered_voices
except (aiohttp.ClientError, aiohttp.ClientResponseError) as e:
if attempt < max_retries - 1:
- logger.error(f"Edge TTS服务连接失败,第{attempt + 1}次重试中... 错误: {str(e)!r}")
+ logger.error(
+ f"Edge TTS服务连接失败,第{attempt + 1}次重试中... 错误: {str(e)!r}"
+ )
await asyncio.sleep(retry_delay)
continue
else:
- logger.error(f"Edge TTS服务连接失败,已重试{max_retries}次: {str(e)!r}")
+ logger.error(
+ f"Edge TTS服务连接失败,已重试{max_retries}次: {str(e)!r}"
+ )
except KeyError as e:
logger.error(f"Edge TTS语音解析失败: {str(e)!r}")
break
except Exception as e:
if attempt < max_retries - 1:
- logger.error(f"Edge TTS语音获取失败,第{attempt + 1}次重试中... 错误: {str(e)!r}")
+ logger.error(
+ f"Edge TTS语音获取失败,第{attempt + 1}次重试中... 错误: {str(e)!r}"
+ )
await asyncio.sleep(retry_delay)
continue
else:
logger.error(f"Edge TTS语音解析失败: {str(e)!r}")
-
+
# 所有尝试都失败后,返回默认语音列表
return self._get_default_voices()
-
+
def _get_default_voices(self):
"""获取默认语音列表"""
- default_voices = [
- {"name": "Xiaoxiao", "id": "zh-CN-XiaoxiaoNeural", "languages": "zh-CN", "full_info": "Female | zh-CN | Type: Neural"},
- {"name": "Yunxi", "id": "zh-CN-YunxiNeural", "languages": "zh-CN", "full_info": "Male | zh-CN | Type: Neural"},
- {"name": "Xiaoyi", "id": "zh-CN-XiaoyiNeural", "languages": "zh-CN", "full_info": "Female | zh-CN | Type: Neural"},
- {"name": "Jenny", "id": "en-US-JennyNeural", "languages": "en-US", "full_info": "Female | en-US | Type: Neural"},
- {"name": "Guy", "id": "en-US-GuyNeural", "languages": "en-US", "full_info": "Male | en-US | Type: Neural"}
- ]
- logger.info("Edge TTS服务不可用,使用默认语音列表")
+ default_voices = [
+ {
+ "name": "Xiaoxiao",
+ "id": "zh-CN-XiaoxiaoNeural",
+ "languages": "zh-CN",
+ "full_info": "Female | zh-CN | Type: Neural",
+ },
+ {
+ "name": "Yunxi",
+ "id": "zh-CN-YunxiNeural",
+ "languages": "zh-CN",
+ "full_info": "Male | zh-CN | Type: Neural",
+ },
+ {
+ "name": "Xiaoyi",
+ "id": "zh-CN-XiaoyiNeural",
+ "languages": "zh-CN",
+ "full_info": "Female | zh-CN | Type: Neural",
+ },
+ {
+ "name": "Jenny",
+ "id": "en-US-JennyNeural",
+ "languages": "en-US",
+ "full_info": "Female | en-US | Type: Neural",
+ },
+ {
+ "name": "Guy",
+ "id": "en-US-GuyNeural",
+ "languages": "en-US",
+ "full_info": "Male | en-US | Type: Neural",
+ },
+ ]
+ logger.info("Edge TTS服务不可用,使用默认语音列表")
return default_voices
+
class basic_settings_volume(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
@@ -234,35 +317,72 @@ def __init__(self, parent=None):
self.volume_size.setFixedWidth(WIDTH_SPINBOX)
self.volume_size.setRange(0, 100)
self.volume_size.setSuffix("%")
- self.volume_size.setValue(int(readme_settings_async("basic_voice_settings", "volume_size")))
- self.volume_size.valueChanged.connect(lambda value: update_settings("basic_voice_settings", "volume_size", value))
+ self.volume_size.setValue(
+ int(readme_settings_async("basic_voice_settings", "volume_size"))
+ )
+ self.volume_size.valueChanged.connect(
+ lambda value: update_settings("basic_voice_settings", "volume_size", value)
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_speaker_edit_20_filled"),
- get_content_name_async("basic_voice_settings", "volume_size"), get_content_description_async("basic_voice_settings", "volume_size"), self.volume_size)
+ self.addGroup(
+ get_theme_icon("ic_fluent_speaker_edit_20_filled"),
+ get_content_name_async("basic_voice_settings", "volume_size"),
+ get_content_description_async("basic_voice_settings", "volume_size"),
+ self.volume_size,
+ )
+
class basic_settings_system_volume(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.setTitle(get_content_name_async("basic_voice_settings", "system_volume_group"))
+ self.setTitle(
+ get_content_name_async("basic_voice_settings", "system_volume_group")
+ )
self.setBorderRadius(8)
# 系统音量控制类型设置
self.system_volume_control = ComboBox()
- self.system_volume_control.addItems(get_content_combo_name_async("basic_voice_settings", "system_volume_control"))
- self.system_volume_control.setCurrentText(readme_settings_async("basic_voice_settings", "system_volume_control"))
- self.system_volume_control.currentTextChanged.connect(lambda text: update_settings("basic_voice_settings", "system_volume_control", text))
+ self.system_volume_control.addItems(
+ get_content_combo_name_async(
+ "basic_voice_settings", "system_volume_control"
+ )
+ )
+ self.system_volume_control.setCurrentText(
+ readme_settings_async("basic_voice_settings", "system_volume_control")
+ )
+ self.system_volume_control.currentTextChanged.connect(
+ lambda text: update_settings(
+ "basic_voice_settings", "system_volume_control", text
+ )
+ )
# 系统音量大小设置
self.system_volume_size = SpinBox()
self.system_volume_size.setFixedWidth(WIDTH_SPINBOX)
self.system_volume_size.setRange(0, 100)
self.system_volume_size.setSuffix("%")
- self.system_volume_size.setValue(int(readme_settings_async("basic_voice_settings", "system_volume_size")))
- self.system_volume_size.valueChanged.connect(lambda value: update_settings("basic_voice_settings", "system_volume_size", value))
+ self.system_volume_size.setValue(
+ int(readme_settings_async("basic_voice_settings", "system_volume_size"))
+ )
+ self.system_volume_size.valueChanged.connect(
+ lambda value: update_settings(
+ "basic_voice_settings", "system_volume_size", value
+ )
+ )
# 添加设置项到分组
- self.addGroup(get_theme_icon("ic_fluent_speaker_settings_20_filled"),
- get_content_name_async("basic_voice_settings", "system_volume_control"), get_content_description_async("basic_voice_settings", "system_volume_control"), self.system_volume_control)
- self.addGroup(get_theme_icon("ic_fluent_speaker_edit_20_filled"),
- get_content_name_async("basic_voice_settings", "system_volume_size"), get_content_description_async("basic_voice_settings", "system_volume_size"), self.system_volume_size)
+ self.addGroup(
+ get_theme_icon("ic_fluent_speaker_settings_20_filled"),
+ get_content_name_async("basic_voice_settings", "system_volume_control"),
+ get_content_description_async(
+ "basic_voice_settings", "system_volume_control"
+ ),
+ self.system_volume_control,
+ )
+ self.addGroup(
+ get_theme_icon("ic_fluent_speaker_edit_20_filled"),
+ get_content_name_async("basic_voice_settings", "system_volume_size"),
+ get_content_description_async("basic_voice_settings", "system_volume_size"),
+ self.system_volume_size,
+ )
diff --git a/app/view/settings/voice_settings/specific_announcements.py b/app/view/settings/voice_settings/specific_announcements.py
index a19bdd94..b826c49b 100644
--- a/app/view/settings/voice_settings/specific_announcements.py
+++ b/app/view/settings/voice_settings/specific_announcements.py
@@ -1,16 +1,11 @@
# ==================================================
# 导入库
# ==================================================
-import json
-import os
-import sys
-import subprocess
-from loguru import logger
-from PyQt6.QtWidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
+from PySide6.QtWidgets import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from PySide6.QtNetwork import *
from qfluentwidgets import *
from app.tools.variable import *
@@ -19,4 +14,3 @@
from app.tools.settings_default import *
from app.tools.settings_access import *
from app.Language.obtain_language import *
-
diff --git a/app/view/tray/__init__.py b/app/view/tray/__init__.py
new file mode 100644
index 00000000..2b0133cc
--- /dev/null
+++ b/app/view/tray/__init__.py
@@ -0,0 +1 @@
+"""System tray views package."""
diff --git a/app/view/tray/tray.py b/app/view/tray/tray.py
index 6a5927a0..74a259c7 100644
--- a/app/view/tray/tray.py
+++ b/app/view/tray/tray.py
@@ -1,84 +1,86 @@
# ==================================================
# 导入库
# ==================================================
-import json
-import os
-import sys
-import subprocess
-
-from loguru import logger
-from PyQt6.QtWidgets import *
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtNetwork import *
-from qfluentwidgets import *
-
-from app.tools.variable import *
-from app.tools.path_utils import *
-from app.tools.personalised import *
-from app.tools.settings_default import *
-from app.tools.settings_access import *
-from app.Language.obtain_language import *
-
-from app.tools.extract import _is_non_class_time
+
+from PySide6.QtWidgets import QApplication, QSystemTrayIcon
+from PySide6.QtGui import QIcon, QCursor
+from PySide6.QtCore import QTimer, QEvent, QPoint, Signal
+from qfluentwidgets import RoundMenu, Action
+
+from app.tools.variable import MENU_AUTO_CLOSE_TIMEOUT
+from app.tools.path_utils import get_resources_path
+
# ==================================================
# 托盘图标管理器类
# ==================================================
class Tray(QSystemTrayIcon):
"""系统托盘图标管理器
-
+
负责管理托盘图标和菜单,提供右键菜单功能。
继承自QSystemTrayIcon以简化实现。
"""
- showSettingsRequested = pyqtSignal()
- showSettingsRequestedAbout = pyqtSignal()
-
+
+ showSettingsRequested = Signal()
+ showSettingsRequestedAbout = Signal()
+
def __init__(self, parent=None):
"""初始化系统托盘图标
-
+
Args:
parent: 父窗口对象,通常为主窗口
"""
super().__init__(parent)
self.main_window = parent
- self.setIcon(QIcon(str(get_resources_path('assets/icon', 'secrandom-icon-paper.png'))))
- self.setToolTip('SecRandom')
+ self.setIcon(
+ QIcon(str(get_resources_path("assets/icon", "secrandom-icon-paper.png")))
+ )
+ self.setToolTip("SecRandom")
self._create_menu()
self.activated.connect(self._on_tray_activated)
-
+
# 初始化菜单自动关闭定时器
self._init_menu_timer()
-
+
def _init_menu_timer(self):
"""初始化菜单自动关闭定时器"""
self.menu_timer = QTimer(self)
self.menu_timer.setSingleShot(True)
self.menu_timer.timeout.connect(self._on_menu_timeout)
-
+
def _create_menu(self):
"""创建托盘右键菜单"""
self.tray_menu = RoundMenu(parent=self.main_window)
-
+
# 关于SecRandom
- self.tray_menu.addAction(Action('SecRandom', triggered=self.showSettingsRequestedAbout.emit))
+ self.tray_menu.addAction(
+ Action("SecRandom", triggered=self.showSettingsRequestedAbout.emit)
+ )
self.tray_menu.addSeparator()
# 主界面控制
- self.tray_menu.addAction(Action('暂时显示/隐藏主界面', triggered=self.main_window.toggle_window))
+ self.tray_menu.addAction(
+ Action("暂时显示/隐藏主界面", triggered=self.main_window.toggle_window)
+ )
# 设置界面
- self.tray_menu.addAction(Action('打开设置界面', triggered=self.showSettingsRequested.emit))
+ self.tray_menu.addAction(
+ Action("打开设置界面", triggered=self.showSettingsRequested.emit)
+ )
self.tray_menu.addSeparator()
# 系统操作
- self.tray_menu.addAction(Action('重启', triggered=self.main_window.restart_app))
- self.tray_menu.addAction(Action('退出', triggered=self.main_window.close_window_secrandom))
-
+ self.tray_menu.addAction(Action("重启", triggered=self.main_window.restart_app))
+ self.tray_menu.addAction(
+ Action("退出", triggered=self.main_window.close_window_secrandom)
+ )
+
self.tray_menu.installEventFilter(self)
-
+
def _on_tray_activated(self, reason):
"""处理托盘图标点击事件
当用户点击托盘图标时,显示菜单"""
- if reason in (QSystemTrayIcon.ActivationReason.Trigger,
- QSystemTrayIcon.ActivationReason.Context):
+ if reason in (
+ QSystemTrayIcon.ActivationReason.Trigger,
+ QSystemTrayIcon.ActivationReason.Context,
+ ):
pos = QCursor.pos()
screen = QApplication.primaryScreen().availableGeometry()
menu_size = self.tray_menu.sizeHint()
@@ -90,35 +92,38 @@ def _on_tray_activated(self, reason):
adjusted_y = pos.y() - menu_size.height()
else:
adjusted_y = pos.y()
- adjusted_x = max(screen.left(), min(adjusted_x, screen.right() - menu_size.width()))
- adjusted_y = max(screen.top(), min(adjusted_y, screen.bottom() - menu_size.height()))
+ adjusted_x = max(
+ screen.left(), min(adjusted_x, screen.right() - menu_size.width())
+ )
+ adjusted_y = max(
+ screen.top(), min(adjusted_y, screen.bottom() - menu_size.height())
+ )
adjusted_pos = QPoint(adjusted_x, adjusted_y - 35)
self.tray_menu.popup(adjusted_pos)
self.menu_timer.start(MENU_AUTO_CLOSE_TIMEOUT)
-
+
def _on_menu_timeout(self):
"""菜单超时自动关闭
当用户5秒内没有操作菜单时,自动关闭菜单"""
if self.tray_menu.isVisible():
self.tray_menu.close()
-
+
def eventFilter(self, obj, event):
"""事件过滤器
-
+
监听菜单相关事件,当用户点击菜单外部时自动关闭菜单。
-
+
Args:
obj: 事件对象
event: 事件类型
-
+
Returns:
bool: 是否拦截事件
"""
if obj == self.tray_menu:
if event.type() in (QEvent.Type.MouseButtonPress, QEvent.Type.Hide):
self.menu_timer.stop()
- if (event.type() == QEvent.Type.MouseButtonPress and
- self.tray_menu.isVisible()):
+ if event.type() == QEvent.Type.MouseButtonPress and self.tray_menu.isVisible():
click_pos = event.globalPosition().toPoint()
menu_rect = self.tray_menu.geometry()
if not menu_rect.contains(click_pos):
@@ -126,8 +131,8 @@ def eventFilter(self, obj, event):
self.menu_timer.stop()
return True
return super().eventFilter(obj, event)
-
+
def show_tray_icon(self):
"""显示托盘图标"""
if not self.isVisible():
- self.show()
\ No newline at end of file
+ self.show()
diff --git a/build_nuitka.py b/build_nuitka.py
new file mode 100644
index 00000000..5f31a3ad
--- /dev/null
+++ b/build_nuitka.py
@@ -0,0 +1,210 @@
+"""Nuitka packaging helper for SecRandom using the shared packaging utilities."""
+
+from __future__ import annotations
+
+import subprocess
+import sys
+from pathlib import Path
+
+from packaging_utils import (
+ ADDITIONAL_HIDDEN_IMPORTS,
+ ICON_FILE,
+ PROJECT_ROOT,
+ VERSION_FILE,
+ collect_data_includes,
+ collect_language_modules,
+ collect_view_modules,
+ normalize_hidden_imports,
+)
+
+
+PACKAGE_INCLUDE_NAMES = {
+ "app.Language.modules",
+ "app.view",
+ "app.tools",
+ "app.page_building",
+}
+
+
+def _read_version() -> str:
+ try:
+ return VERSION_FILE.read_text(encoding="utf-8").strip()
+ except FileNotFoundError:
+ return "0.0.0.0"
+
+
+def _print_packaging_summary() -> None:
+ data_includes = collect_data_includes()
+ hidden_names = normalize_hidden_imports(
+ collect_language_modules() + collect_view_modules() + ADDITIONAL_HIDDEN_IMPORTS
+ )
+
+ package_names = sorted(
+ {name for name in hidden_names if "." not in name} | PACKAGE_INCLUDE_NAMES
+ )
+ module_names = [name for name in hidden_names if "." in name]
+
+ print("\nSelected data includes ({} entries):".format(len(data_includes)))
+ for item in data_includes:
+ kind = "dir " if item.is_dir else "file"
+ print(f" - {kind} {item.source} -> {item.target}")
+
+ print("\nRequired packages ({} entries):".format(len(package_names)))
+ for pkg in package_names:
+ print(f" - {pkg}")
+
+ print("\nHidden modules ({} entries):".format(len(module_names)))
+ for mod in module_names:
+ print(f" - {mod}")
+
+
+def _gather_data_flags() -> list[str]:
+ flags: list[str] = []
+ for include in collect_data_includes():
+ flag = "--include-data-dir" if include.is_dir else "--include-data-file"
+ flags.append(f"{flag}={include.source}={include.target}")
+ return flags
+
+
+def _gather_module_and_package_flags() -> tuple[list[str], list[str]]:
+ hidden_names = normalize_hidden_imports(
+ collect_language_modules() + collect_view_modules() + ADDITIONAL_HIDDEN_IMPORTS
+ )
+
+ package_names = set(PACKAGE_INCLUDE_NAMES)
+ module_names: list[str] = []
+
+ for name in hidden_names:
+ if "." not in name:
+ package_names.add(name)
+ else:
+ module_names.append(name)
+
+ package_flags = [f"--include-package={pkg}" for pkg in sorted(package_names)]
+ module_flags = [f"--include-module={mod}" for mod in module_names]
+ return module_flags, package_flags
+
+
+def get_nuitka_command():
+ """生成 Nuitka 打包命令"""
+
+ version = _read_version()
+
+ module_flags, package_flags = _gather_module_and_package_flags()
+
+ cmd = [
+ sys.executable,
+ "-m",
+ "nuitka",
+ "--standalone",
+ "--onefile",
+ "--enable-plugin=pyside6",
+ "--assume-yes-for-downloads",
+ # 使用 MinGW64 编译器
+ "--mingw64",
+ # 输出目录
+ "--output-dir=dist",
+ # 应用程序信息
+ "--product-name=SecRandom",
+ "--file-description=公平随机抽取系统",
+ f"--product-version={version}",
+ "--copyright=Copyright (c) 2025",
+ # **修复 QFluentWidgets 方法签名检测问题**
+ # Nuitka 在 standalone 模式下会改变代码执行环境,
+ # 导致 QFluentWidgets 的 overload.py 签名检测失败
+ "--no-deployment-flag=self-execution",
+ ]
+
+ cmd.extend(_gather_data_flags())
+ cmd.extend(package_flags)
+ cmd.extend(module_flags)
+
+ if ICON_FILE.exists():
+ cmd.append(f"--windows-icon-from-ico={ICON_FILE}")
+
+ # 主入口文件
+ cmd.append("main.py")
+
+ return cmd
+
+
+def check_mingw64():
+ """检查 MinGW64 是否可用"""
+ print("\n检查 MinGW64 环境...")
+
+ # 检查是否在 PATH 中
+ gcc_path = None
+ try:
+ result = subprocess.run(
+ ["gcc", "--version"], capture_output=True, text=True, check=False
+ )
+ if result.returncode == 0:
+ gcc_path = "gcc (在 PATH 中)"
+ print(f"✓ 找到 GCC: {gcc_path}")
+ print(f" 版本信息: {result.stdout.splitlines()[0]}")
+ return True
+ except FileNotFoundError:
+ pass
+
+ # 检查常见的 MinGW64 安装位置
+ common_paths = [
+ r"C:\msys64\mingw64\bin",
+ r"C:\mingw64\bin",
+ r"C:\Program Files\mingw64\bin",
+ r"C:\msys64\ucrt64\bin",
+ ]
+
+ for path in common_paths:
+ gcc_exe = Path(path) / "gcc.exe"
+ if gcc_exe.exists():
+ print(f"✓ 找到 MinGW64: {path}")
+ print(f" 提示: 请确保 {path} 在系统 PATH 环境变量中")
+ return True
+
+ print("⚠ 警告: 未找到 MinGW64")
+ print("\n请按照以下步骤安装 MinGW64:")
+ print("1. 下载 MSYS2: https://www.msys2.org/")
+ print("2. 安装后运行: pacman -S mingw-w64-x86_64-gcc")
+ print("3. 将 C:\\msys64\\mingw64\\bin 添加到系统 PATH")
+ print("\n或者使用 Nuitka 自动下载 MinGW64 (首次运行会自动下载)")
+
+ response = input("\n是否继续? Nuitka 可以自动下载 MinGW64 (y/n): ")
+ return response.lower() == "y"
+
+
+def main():
+ """执行打包"""
+ print("=" * 60)
+ print("开始使用 Nuitka + MinGW64 打包 SecRandom")
+ print("=" * 60)
+
+ # 检查 MinGW64
+ if not check_mingw64():
+ print("\n取消打包")
+ sys.exit(1)
+
+ _print_packaging_summary()
+
+ # 生成命令
+ cmd = get_nuitka_command()
+
+ # 打印命令
+ print("\n执行命令:")
+ print(" ".join(cmd))
+ print("\n" + "=" * 60)
+
+ # 执行打包
+ try:
+ subprocess.run(cmd, check=True, cwd=PROJECT_ROOT)
+ print("\n" + "=" * 60)
+ print("打包成功!")
+ print("=" * 60)
+ except subprocess.CalledProcessError as e:
+ print("\n" + "=" * 60)
+ print(f"打包失败: {e}")
+ print("=" * 60)
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/build_pyinstaller.py b/build_pyinstaller.py
new file mode 100644
index 00000000..10a38689
--- /dev/null
+++ b/build_pyinstaller.py
@@ -0,0 +1,82 @@
+"""
+PyInstaller 打包脚本
+用于构建 SecRandom 的独立可执行文件
+"""
+
+import subprocess
+import sys
+from pathlib import Path
+
+from packaging_utils import (
+ ADDITIONAL_HIDDEN_IMPORTS,
+ collect_data_includes,
+ collect_language_modules,
+ collect_view_modules,
+ normalize_hidden_imports,
+)
+
+# 获取项目根目录
+PROJECT_ROOT = Path(__file__).parent
+SPEC_FILE = PROJECT_ROOT / "Secrandom.spec"
+
+
+def _print_packaging_summary() -> None:
+ """Log a quick overview of the resources and modules that will be bundled."""
+
+ data_includes = collect_data_includes()
+ hidden_imports = normalize_hidden_imports(
+ collect_language_modules() + collect_view_modules() + ADDITIONAL_HIDDEN_IMPORTS
+ )
+
+ print("\nSelected data includes ({} entries):".format(len(data_includes)))
+ for item in data_includes:
+ kind = "dir " if item.is_dir else "file"
+ print(f" - {kind} {item.source} -> {item.target}")
+
+ print("\nHidden imports ({} modules):".format(len(hidden_imports)))
+ for name in hidden_imports:
+ print(f" - {name}")
+
+
+def main():
+ """执行 PyInstaller 打包"""
+ print("=" * 60)
+ print("开始使用 PyInstaller 打包 SecRandom")
+ print("=" * 60)
+
+ if not SPEC_FILE.exists():
+ print("\nSecrandom.spec 不存在,请先生成或恢复该文件。")
+ sys.exit(1)
+
+ _print_packaging_summary()
+
+ cmd = [
+ sys.executable,
+ "-m",
+ "PyInstaller",
+ "Secrandom.spec",
+ "--clean",
+ "--noconfirm",
+ ]
+
+ # 打印命令
+ print("\n执行命令:")
+ print(" ".join(cmd))
+ print("\n" + "=" * 60)
+
+ # 执行打包
+ try:
+ subprocess.run(cmd, check=True, cwd=PROJECT_ROOT)
+ print("\n" + "=" * 60)
+ print("打包成功!")
+ print("可执行文件位于: dist/SecRandom.exe")
+ print("=" * 60)
+ except subprocess.CalledProcessError as e:
+ print("\n" + "=" * 60)
+ print(f"打包失败: {e}")
+ print("=" * 60)
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/main.py b/main.py
index 9a1ad213..c92155a2 100644
--- a/main.py
+++ b/main.py
@@ -3,27 +3,28 @@
# ==================================================
import os
import sys
-import json
import time
-import multiprocessing
-from PyQt6.QtGui import *
-from PyQt6.QtCore import *
-from PyQt6.QtWidgets import *
-from PyQt6.QtNetwork import *
+from PySide6.QtGui import *
+from PySide6.QtCore import *
+from PySide6.QtWidgets import *
+from PySide6.QtNetwork import *
from qfluentwidgets import *
from loguru import logger
-from pathlib import Path
from app.tools.variable import *
from app.tools.path_utils import *
from app.tools.settings_default import *
from app.tools.settings_access import *
-
from app.Language.obtain_language import *
+from app.tools.config import *
+
+# 全局窗口引用(延迟创建)
+main_window = None
+settings_window = None
-from app.view.main.window import MainWindow
-from app.view.settings.settings import SettingsWindow
+# 全局变量,用于存储本地服务器实例
+local_server = None
# 添加项目根目录到Python路径
@@ -39,7 +40,7 @@ def configure_logging():
# 确保日志目录存在
log_dir = get_path(LOG_DIR)
log_dir.mkdir(exist_ok=True)
-
+
# 配置日志格式
logger.add(
log_dir / LOG_FILENAME_FORMAT,
@@ -48,9 +49,10 @@ def configure_logging():
compression=LOG_COMPRESSION, # 启用压缩
backtrace=True, # 启用回溯信息
diagnose=True, # 启用诊断信息
- catch=True # 捕获未处理的异常
+ catch=True, # 捕获未处理的异常
)
+
# ==================================================
# 显示调节
# ==================================================
@@ -58,10 +60,10 @@ def configure_logging():
def configure_dpi_scale():
"""配置DPI缩放模式"""
dpiScale = readme_settings("basic_settings", "dpiScale")
-
- if dpiScale == "Auto":
+ if dpiScale == get_content_combo_name_async("basic_settings", "dpiScale")[-1]:
QApplication.setHighDpiScaleFactorRoundingPolicy(
- Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
+ Qt.HighDpiScaleFactorRoundingPolicy.PassThrough
+ )
os.environ["QT_ENABLE_HIGHDPI_SCALING"] = "1"
logger.debug("DPI缩放已设置为自动模式")
else:
@@ -69,37 +71,87 @@ def configure_dpi_scale():
os.environ["QT_SCALE_FACTOR"] = str(dpiScale)
logger.debug(f"DPI缩放已设置为{dpiScale}倍")
+
# ==================================================
# 单实例检查相关函数
# ==================================================
def check_single_instance():
"""检查单实例,防止多个程序副本同时运行
-
+
Returns:
- QSharedMemory: 共享内存对象
+ tuple: (QSharedMemory, bool) 共享内存对象和是否为第一个实例
"""
shared_memory = QSharedMemory(SHARED_MEMORY_KEY)
if not shared_memory.create(1):
- logger.info('检测到已有 SecRandom 实例正在运行')
+ logger.info("检测到已有 SecRandom 实例正在运行,尝试激活已有实例")
+ # 尝试附加到共享内存
+ if shared_memory.attach():
+ # 尝试通过本地套接字激活已有实例
+ try:
+ local_socket = QLocalSocket()
+ local_socket.connectToServer(SHARED_MEMORY_KEY)
+ if local_socket.waitForConnected(1000):
+ # 发送激活窗口的信号
+ local_socket.write(b"activate")
+ local_socket.flush()
+ local_socket.waitForBytesWritten(1000)
+ logger.info("已发送激活信号到已有实例")
+ local_socket.disconnectFromServer()
+ except Exception as e:
+ logger.error(f"激活已有实例失败: {e}")
+ finally:
+ return shared_memory, False
+ else:
+ logger.error("无法附加到共享内存")
+ return shared_memory, False
+
+ logger.info("单实例检查通过,可以安全启动程序")
+ return shared_memory, True
+
+
+def setup_local_server():
+ """设置本地服务器,用于接收激活窗口的信号
- logger.info('单实例检查通过,可以安全启动程序')
+ Returns:
+ QLocalServer: 本地服务器对象
+ """
+ server = QLocalServer()
+ if not server.listen(SHARED_MEMORY_KEY):
+ logger.error(f"无法启动本地服务器: {server.errorString()}")
+ return None
+
+ def handle_new_connection():
+ """处理新的连接请求"""
+ socket = server.nextPendingConnection()
+ if socket:
+ if socket.waitForReadyRead(1000):
+ data = socket.readAll()
+ if data == b"activate":
+ # 激活主窗口
+ if main_window:
+ main_window.show()
+ main_window.raise_()
+ main_window.activateWindow()
+ logger.info("已激活主窗口")
+ socket.disconnectFromServer()
- return shared_memory
+ server.newConnection.connect(handle_new_connection)
+ logger.info("本地服务器已启动,等待激活信号")
+ return server
# ==================================================
# 字体设置相关函数
# ==================================================
def apply_font_settings():
"""应用字体设置 - 优化版本,使用字体管理器异步加载"""
- from app.tools.settings_access import readme_settings
font_family = readme_settings("basic_settings", "font")
-
+
setFontFamilies([font_family])
QTimer.singleShot(FONT_APPLY_DELAY, lambda: apply_font_to_application(font_family))
def apply_font_to_application(font_family):
"""应用字体设置到整个应用程序,优化版本使用字体管理器
-
+
Args:
font_family (str): 字体家族名称
"""
@@ -114,26 +166,29 @@ def apply_font_to_application(font_family):
widgets_updated += 1
else:
widgets_skipped += 1
- logger.debug(f"已应用字体: {font_family}, 更新了{widgets_updated}个控件字体, 跳过了{widgets_skipped}个已有相同字体的控件")
+ logger.debug(
+ f"已应用字体: {font_family}, 更新了{widgets_updated}个控件字体, 跳过了{widgets_skipped}个已有相同字体的控件"
+ )
except Exception as e:
logger.error(f"应用字体失败: {e}")
+
def update_widget_fonts(widget, font, font_family):
"""更新控件及其子控件的字体,优化版本减少内存占用,特别处理ComboBox等控件
-
+
Args:
widget: 要更新字体的控件
font: 要应用的字体
font_family: 目标字体家族名称
-
+
Returns:
bool: 是否更新了控件的字体
"""
if widget is None:
return False
-
+
try:
- if not hasattr(widget, 'font') or not hasattr(widget, 'setFont'):
+ if not hasattr(widget, "font") or not hasattr(widget, "setFont"):
return False
current_widget_font = widget.font()
if current_widget_font.family() == font_family:
@@ -157,51 +212,75 @@ def update_widget_fonts(widget, font, font_family):
logger.error(f"更新控件字体时发生异常: {e}")
return False
+
def start_main_window():
"""创建主窗口实例"""
global main_window
try:
+ # 延迟导入主窗口类,避免在模块导入阶段加载大量UI代码
+ from app.view.main.window import MainWindow
+
main_window = MainWindow()
- main_window.showSettingsRequested.connect(show_settings_window)
- main_window.showSettingsRequestedAbout.connect(show_settings_window_about)
+ main_window.showSettingsRequested.connect(lambda: show_settings_window())
+ main_window.showSettingsRequestedAbout.connect(
+ lambda: show_settings_window_about
+ )
main_window.show()
+ try:
+ elapsed = time.perf_counter() - app_start_time
+ logger.debug(f"主窗口创建并显示完成,启动耗时: {elapsed:.3f}s")
+ except Exception:
+ pass
except Exception as e:
logger.error(f"创建主窗口失败: {e}", exc_info=True)
+
def create_settings_window():
"""创建设置窗口实例"""
global settings_window
try:
+ from app.view.settings.settings import SettingsWindow
settings_window = SettingsWindow()
except Exception as e:
logger.error(f"创建设置窗口失败: {e}", exc_info=True)
+
def show_settings_window():
"""显示设置窗口"""
try:
- settings_window.show_settings_window()
+ global settings_window
+ if settings_window is None:
+ create_settings_window()
+ if settings_window is not None:
+ settings_window.show_settings_window()
except Exception as e:
logger.error(f"显示设置窗口失败: {e}", exc_info=True)
+
def show_settings_window_about():
"""显示关于窗口"""
try:
- settings_window.show_settings_window_about()
+ global settings_window
+ if settings_window is None:
+ create_settings_window()
+ if settings_window is not None:
+ settings_window.show_settings_window_about()
except Exception as e:
logger.error(f"显示关于窗口失败: {e}", exc_info=True)
+
# ==================================================
# 应用程序初始化相关函数
# ==================================================
def initialize_app():
"""初始化应用程序,使用QTimer避免阻塞主线程,实现并行加载"""
program_dir = str(get_app_root())
-
+
# 更改当前工作目录
if os.getcwd() != program_dir:
os.chdir(program_dir)
- logger.info(f"工作目录已设置为: {program_dir}")
-
+ logger.debug(f"工作目录已设置为: {program_dir}")
+
# 并行加载资源
# 管理设置文件,确保其存在且完整
manage_settings_file()
@@ -210,29 +289,42 @@ def initialize_app():
configure_dpi_scale()
# 加载主题
- QTimer.singleShot(APP_INIT_DELAY, lambda: (
- setTheme({"LIGHT": Theme.LIGHT, "DARK": Theme.DARK, "AUTO": Theme.AUTO}.get(readme_settings("basic_settings", "theme"), Theme.LIGHT))
- ))
+ QTimer.singleShot(
+ APP_INIT_DELAY,
+ lambda: (
+ # 读取主题设置并安全映射到Theme
+ (
+ lambda: (
+ setTheme(Theme.DARK)
+ if readme_settings("basic_settings", "theme") == "DARK"
+ else (
+ setTheme(Theme.AUTO)
+ if readme_settings("basic_settings", "theme") == "AUTO"
+ else setTheme(Theme.LIGHT)
+ )
+ )
+ )()
+ ),
+ )
# 加载主题颜色
- QTimer.singleShot(APP_INIT_DELAY, lambda: (
- setThemeColor(readme_settings("basic_settings", "theme_color"))
- ))
+ QTimer.singleShot(
+ APP_INIT_DELAY,
+ lambda: (setThemeColor(readme_settings("basic_settings", "theme_color"))),
+ )
- # 创建主窗口实例
- QTimer.singleShot(APP_INIT_DELAY, lambda: (
- start_main_window()
- ))
+ # 清除重启记录
+ QTimer.singleShot(APP_INIT_DELAY, lambda: (remove_record("", "", "", "restart")))
- # 创建设置窗口实例
- QTimer.singleShot(APP_INIT_DELAY, lambda: (
- create_settings_window()
- ))
+ # 创建主窗口实例
+ QTimer.singleShot(APP_INIT_DELAY, lambda: (start_main_window()))
# 应用字体设置
- QTimer.singleShot(APP_INIT_DELAY, lambda: (
- apply_font_settings()
- ))
+ QTimer.singleShot(APP_INIT_DELAY, lambda: (apply_font_settings()))
+
+ # 记录初始化完成时间(辅助诊断)
+ logger.debug("应用初始化调度已启动,主窗口将在延迟后创建")
+
# ==================================================
# 主程序入口
@@ -241,31 +333,55 @@ def main_async():
"""主异步函数,用于启动应用程序"""
QTimer.singleShot(APP_INIT_DELAY, initialize_app)
+
if __name__ == "__main__":
- app = QApplication(sys.argv)
+ # 记录应用启动时间,用于诊断各阶段耗时
+ app_start_time = time.perf_counter()
+
+ # 首先进行单实例检查
+ shared_memory, is_first_instance = check_single_instance()
+ if not is_first_instance:
+ # 不是第一个实例,退出程序
+ logger.info("程序将退出,已有实例已激活")
+ sys.exit(0)
+
+ # 设置本地服务器,用于接收激活窗口的信号
+ local_server = setup_local_server()
+ if not local_server:
+ logger.error("无法启动本地服务器,程序将退出")
+ shared_memory.detach()
+ sys.exit(1)
+
+ app = QApplication(sys.argv)
+
import gc
+
gc.enable()
app.setQuitOnLastWindowClosed(APP_QUIT_ON_LAST_WINDOW_CLOSED)
-
+
# 解决Dialog和FluentWindow共存时的窗口拉伸问题
app.setAttribute(Qt.ApplicationAttribute.AA_DontCreateNativeWidgetSiblings)
-
+
try:
# 首先配置日志系统
configure_logging()
-
+
# 初始化应用程序
main_async()
-
+
app.exec()
+
+ # 程序退出时释放共享内存
+ shared_memory.detach()
- # 停止系统主题监听器
- stop_system_theme_listener()
-
+ # 关闭本地服务器
+ if local_server:
+ local_server.close()
+
gc.collect()
-
+
sys.exit()
except Exception as e:
print(f"应用程序启动失败: {e}")
@@ -273,4 +389,15 @@ def main_async():
logger.error(f"应用程序启动失败: {e}", exc_info=True)
except:
pass
- sys.exit(1)
\ No newline at end of file
+ # 程序异常退出时释放共享内存
+ try:
+ shared_memory.detach()
+ except:
+ pass
+ # 关闭本地服务器
+ try:
+ if local_server:
+ local_server.close()
+ except:
+ pass
+ sys.exit(1)
diff --git a/packaging_utils.py b/packaging_utils.py
new file mode 100644
index 00000000..66b7a06c
--- /dev/null
+++ b/packaging_utils.py
@@ -0,0 +1,130 @@
+"""Shared helpers for packaging scripts (PyInstaller and Nuitka)."""
+
+from __future__ import annotations
+
+import os
+import pkgutil
+from dataclasses import dataclass
+from pathlib import Path
+from typing import Iterable, List
+
+PROJECT_ROOT = Path(__file__).parent
+APP_DIR = PROJECT_ROOT / "app"
+RESOURCES_DIR = APP_DIR / "resources"
+LANGUAGE_MODULES_DIR = APP_DIR / "Language" / "modules"
+VIEW_DIR = APP_DIR / "view"
+LICENSE_FILE = PROJECT_ROOT / "LICENSE"
+VERSION_FILE = PROJECT_ROOT / "version_info.txt"
+ICON_FILE = PROJECT_ROOT / "resources" / "secrandom-icon-paper.ico"
+
+
+@dataclass(frozen=True)
+class DataInclude:
+ """Represents a file or directory that must be bundled with the app."""
+
+ source: Path
+ target: str
+ is_dir: bool = False
+
+
+BASE_HIDDEN_IMPORTS: List[str] = [
+ "app.Language.obtain_language",
+ "app.tools.language_manager",
+ "app.tools.path_utils",
+ "app.tools.variable",
+ "app.tools.settings_access",
+ "app.tools.settings_default",
+ "app.tools.settings_default_storage",
+ "app.tools.personalised",
+ "app.tools.list",
+ "app.tools.history",
+ "app.tools.result_display",
+ "app.tools.extract",
+ "app.tools.config",
+ "app.page_building.main_window_page",
+ "app.page_building.settings_window_page",
+]
+
+ADDITIONAL_HIDDEN_IMPORTS: List[str] = [
+ "qfluentwidgets",
+ "loguru",
+ "edge_tts",
+ "aiohttp",
+ "imageio",
+ "numpy",
+ "pandas",
+ "PySide6",
+ "app.view.another_window.contributor",
+ "app.view.settings.settings",
+ "app.view.tray.tray",
+]
+
+
+def collect_language_modules() -> List[str]:
+ modules: List[str] = []
+ if LANGUAGE_MODULES_DIR.exists():
+ for file in LANGUAGE_MODULES_DIR.glob("*.py"):
+ if file.name == "__init__.py":
+ continue
+ modules.append(f"app.Language.modules.{file.stem}")
+ return modules
+
+
+def collect_view_modules() -> List[str]:
+ modules: List[str] = []
+ if VIEW_DIR.exists():
+ for _, name, ispkg in pkgutil.walk_packages(
+ [str(VIEW_DIR)], prefix="app.view."
+ ):
+ if not ispkg:
+ modules.append(name)
+ return modules
+
+
+def collect_data_includes() -> List[DataInclude]:
+ includes: List[DataInclude] = []
+ if RESOURCES_DIR.exists():
+ includes.append(DataInclude(RESOURCES_DIR, "app/resources", is_dir=True))
+ if LANGUAGE_MODULES_DIR.exists():
+ includes.append(
+ DataInclude(LANGUAGE_MODULES_DIR, "app/Language/modules", is_dir=True)
+ )
+ if LICENSE_FILE.exists():
+ includes.append(DataInclude(LICENSE_FILE, ".", is_dir=False))
+ return includes
+
+
+def normalize_hidden_imports(extra: Iterable[str] = ()) -> List[str]:
+ seen = set()
+ ordered: List[str] = []
+ for name in list(BASE_HIDDEN_IMPORTS) + list(extra):
+ if name in seen:
+ continue
+ seen.add(name)
+ ordered.append(name)
+ return ordered
+
+
+def format_add_data(include: DataInclude) -> str:
+ sep = ";" if os.name == "nt" else ":"
+ return f"{include.source}{sep}{include.target}"
+
+
+__all__ = [
+ "PROJECT_ROOT",
+ "APP_DIR",
+ "RESOURCES_DIR",
+ "LANGUAGE_MODULES_DIR",
+ "VIEW_DIR",
+ "LICENSE_FILE",
+ "VERSION_FILE",
+ "ICON_FILE",
+ "DataInclude",
+ "BASE_HIDDEN_IMPORTS",
+ "ADDITIONAL_HIDDEN_IMPORTS",
+ "collect_language_modules",
+ "collect_view_modules",
+ "collect_data_includes",
+ "normalize_hidden_imports",
+ "format_add_data",
+]
diff --git a/pyproject.toml b/pyproject.toml
index 4e339ce6..fd2f34d3 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -6,48 +6,37 @@ readme = "README.md"
requires-python = "==3.8.10"
dependencies = [
- # GUI框架
- "PyQt6==6.7.1",
- "PyQt6-Qt6==6.7.3",
- "PyQt6_sip==13.8.0",
- "PyQt6-Fluent-Widgets==1.9.1",
- "PyQt6-Frameless-Window==0.7.4",
+ "PySide6-Fluent-Widgets==1.9.1",
+ "pyside6>=6.6.3.1",
+ "pysidesix-frameless-window>=0.7.4",
"darkdetect==0.8.0",
-
# 核心库
"asyncio~=3.4.3",
"loguru==0.7.3",
"colorama==0.4.6",
"packaging==25.0",
-
# 数据处理
"numpy==1.24.4",
"pandas~=2.0.3",
"pillow~=10.4.0",
"openpyxl==3.1.5",
-
# 网络与通信
"requests==2.32.4",
"edge-tts==7.0.2",
-
# 音频处理
"pyttsx3==2.98",
"sounddevice==0.5.2",
"soundfile==0.13.1",
-
# 系统工具
"psutil~=7.0.0",
"keyboard==0.13.5",
-
# 加密与安全
"pyotp==2.9.0",
"pycryptodome==3.23.0",
-
# 二维码处理
"pyqrcode~=1.2.1",
"pypng~=0.20220715.0",
"colorthief==0.2.1",
-
# Windows特定依赖
"pywin32==310; platform_system == 'Windows'",
"win32_setctime==1.2.0; platform_system == 'Windows'",
@@ -55,13 +44,14 @@ dependencies = [
"comtypes==1.4.10; platform_system == 'Windows'",
"wmi==1.5.1; platform_system == 'Windows'",
"pycaw==20240210; platform_system == 'Windows'",
-
# Linux特定依赖
"pulsectl==24.8.0; platform_system == 'Linux'",
-
# 其他依赖
"sip~=6.8.6",
"jinja2~=3.1.6",
+ "nuitka>=2.8.4",
+ "imageio>=2.35.1",
+ "pre-commit>=3.5.0",
]
[project.optional-dependencies]
@@ -84,4 +74,7 @@ dev-dependencies = [
"pytest>=6.0",
"black",
"flake8",
-]
\ No newline at end of file
+ "pyright>=1.1.407",
+ "pre-commit>=3.5.0",
+ "ruff>=0.14.3",
+]
diff --git a/requirements-linux.txt b/requirements-linux.txt
index c0ea6142..c91cf365 100644
--- a/requirements-linux.txt
+++ b/requirements-linux.txt
@@ -1,11 +1,10 @@
# Linux 依赖配置 - 支持 Python 3.8.10
# === GUI框架 ===
-PyQt6==6.7.1
-PyQt6-Qt6==6.7.3
-PyQt6_sip==13.8.0
-PyQt6-Fluent-Widgets==1.9.1
-PyQt6-Frameless-Window==0.7.4
+PySide6==6.7.1
+PySide6-Qt6==6.7.3
+PySide6-Fluent-Widgets==1.9.1
+PySide6-Frameless-Window==0.7.4
darkdetect==0.8.0
# === 核心库 ===
diff --git a/requirements-windows.txt b/requirements-windows.txt
index 76ca0d52..35039732 100644
--- a/requirements-windows.txt
+++ b/requirements-windows.txt
@@ -1,11 +1,10 @@
# Windows 依赖配置 - 支持 Python 3.8.10
# === GUI框架 ===
-PyQt6==6.7.1
-PyQt6-Qt6==6.7.3
-PyQt6_sip==13.8.0
-PyQt6-Fluent-Widgets==1.9.1
-PyQt6-Frameless-Window==0.7.4
+PySide6==6.7.1
+PySide6-Qt6==6.7.3
+PySide6-Fluent-Widgets==1.9.1
+PySide6-Frameless-Window==0.7.4
darkdetect==0.8.0
# === 核心库 ===
@@ -52,4 +51,4 @@ pycaw==20240210; platform_system == "Windows"
# === 其他依赖 ===
sip~=6.8.6
-jinja2~=3.1.6
\ No newline at end of file
+jinja2~=3.1.6
diff --git a/resources/README_EN.md b/resources/README_EN.md
new file mode 100644
index 00000000..b67b3ce8
--- /dev/null
+++ b/resources/README_EN.md
@@ -0,0 +1,201 @@
+
+
+
+
+# SecRandom - Fair Random Selection System
+
+🚀 **Modern Educational Tool** | 🎯 **Intelligent Weighting Algorithm** | 🎨 **Elegant Interactive Experience**
+
+[简体中文](../README_ZH.md) | **✔English** | [繁體中文](./README_ZH_TW.md)
+> The Readme you are currently reading is **translated by AI** and reviewed by our developers. If you find any errors, please report it.
+
+
+
+
+
+[](https://github.com/SECTL/SecRandom/issues)
+[](https://github.com/SECTL/SecRandom/releases/latest)
+[](https://github.com/SECTL/SecRandom/releases/)
+[](https://github.com/SECTL/SecRandom/commits/master)
+[](https://github.com/SECTL/SecRandom/releases)
+[](https://qm.qq.com/q/iWcfaPHn7W)
+[](https://space.bilibili.com/520571577)
+[](https://opensource.org/licenses/GPL-3.0)
+
+
+
+
+
+> [!note]
+>
+> SecRandom will be open source under the GNU GPLv3 license
+>
+> GNU GPLv3 has Copyleft characteristics, which means you can modify the source code of SecRandom, but **must also open source the modified version under the GNU GPLv3 license**
+---------
+> [!note]
+>
+> **SecRandom v2** will be released around 2025/12/14 (GMT +8:00 China Standard Time)!
+>
+> Please follow our BiliBili / QQ Channel for regular development progress updates!
+
+## 📖 Table of Contents
+
+- [🌟 Core Features](#-core-features)
+- [📥 Download](#-download)
+- [📸 Software Screenshots](#-software-screenshots)
+- [📖 Fair Selection](#-fair-selection)
+- [🙏 Contributors](#-contributors-and-special-thanks)
+- [💝 Support Us](#-support-us)
+- [📞 Contact](#-contact)
+
+## 🌟 Core Features
+
+### 🎯 **Intelligent Fair Selection System**
+
+- ✅ **Dynamic Weighting Algorithm**: Calculates based on multiple dimensions including selection count, group, and gender to ensure true fairness
+- ✅ **Cold Start Protection**: Prevents new members from having too low weight, ensuring everyone has equal opportunities
+- ✅ **Probability Visualization**: Intuitively displays each member's probability of being selected, making the selection process transparent
+
+### 🎨 **Modern User Experience**
+
+- ✅ **Elegant UI Design**: Modern interface based on Fluent Design, supporting light/dark themes
+- ✅ **Floating Window Mode**: Perform selections anytime without affecting other work
+- ✅ **Voice Announcements**: Automatic voice announcement of selection results, supporting custom voice engines
+
+### 🚀 **Powerful Feature Set**
+
+- ✅ **Multiple Selection Modes**: Individual/multiple/group/gender selection to meet different scenario needs
+- ✅ **Smart History Records**: Detailed records with timestamps, supporting automatic cleanup
+- ✅ **Multi-list Management**: Support for importing/exporting lists, easily managing different classes/teams
+
+### 💻 **System Compatibility**
+
+- ✅ **Full Platform Support**: Perfectly compatible with Windows 7/10/11 systems
+- ✅ **Multi-architecture Support**: Native support for x64 and x86 architectures
+- ✅ **Startup on Boot**: Supports automatic startup on boot, always available
+
+## 📥 Download
+
+### 🌐 Official Download Page
+
+- 📥 **[Official Download Page](https://secrandom.netlify.app/download)** - Get the latest stable version and beta versions
+
+### 📦 Download Sources
+
+#### Official Channels
+
+- **GitHub Official Source** - Official release channel, faster access outside mainland China, recommended
+- **123 Cloud Drive Source** - Cloud drive download, no speed limit, faster access in mainland China, suitable for large file downloads
+
+#### Accelerated Mirrors in Mainland China
+
+> [!note]
+>
+> These mirrors are mainly provided for users in mainland China.
+
+- **GitHub Mirror (ghfast.top)** - Accelerated mirror, fast and stable
+- **GitHub Mirror (gh-proxy.com)** - Accelerated mirror, suitable for users with special network environments
+
+## 📸 Software Screenshots
+
+> [!warning]
+>
+> The screenshots shown below are in **Chinese Simplified** language.
+
+
+📸 Software Screenshots Display ✨
+
+
+
+
+
+
+
+
+## 📖 Fair Selection
+
+### Introduction
+
+Fair selection is a random selection method that ensures each member's selection weight is determined by the system, thus avoiding unfair results.
+This method is suitable for scenarios that require random and fair selection of students to answer questions or other situations requiring fair distribution.
+SecRandom's fair selection implementation is based on a dynamic weight system, calculated through multiple aspects.
+
+### Dynamic Weight System
+
+Dynamic weight is the core mechanism of SecRandom's fair selection.
+It calculates each member's weight through the following aspects:
+
+1. **Total Selection Count**: More selections lead to lower weight, avoiding repeated selections
+2. **Group Selection Count**: Balances selection opportunities across different groups
+3. **Gender Selection Count**: Ensures gender balance
+4. **Base Weight**: Customizable initial weight settings
+5. **Cold Start Protection**: Prevents new members from having too low weight, ensuring fairness
+
+## Build and Package
+
+### Trigger Build
+
+Include `进行打包` in the commit message to trigger the automatic build process.
+
+
+
+## 🙏 Contributors and Special Thanks
+
+
+
+
+
+
+## 💝 Support Us
+
+If you find SecRandom helpful, you're welcome to support our development work!
+
+- **Alipay/WeChat Pay**
+
+
+- **Afdian**
+[Charge for 黎泽懿_Aionflux](https://afdian.com/a/lzy0983
+
+## 📞 Contact
+
+* 📧 [Email](mailto:lzy.12@foxmail.com)
+* 👥 [QQ Group 833875216](https://qm.qq.com/q/iWcfaPHn7W)
+* 💬 [QQ Channel](https://pd.qq.com/s/4x5dafd34?b=9)
+* 🎥 [Bilibili Homepage](https://space.bilibili.com/520571577)
+* 🐛 [Issue Report](https://github.com/SECTL/SecRandom/issues)
+
+## 📄 Official Documentation
+
+- 📄 **[SecRandom Official Documentation](https://secrandom.netlify.app)**
+- [](https://deepwiki.com/SECTL/SecRandom)
+
+
+## 🏆 Contribution Points
+
+>[!TIP]
+>
+> 📊 **Contribution Points Formula**: Contribution Points = Documentation new lines x5 + Main program new lines x5 + Issue handling x5
+>
+> 📅 **Statistics Time Range**: 2025.08.01 - 2026.01.31 (China Time UTC+8)
+>
+> 🏗️ **Statistics Repositories**: SECTL/SecRandom, SECTL/SecRandom-docs
+
+## ✨ Star History
+
+
+
+
+
+
+**Copyright © 2025 SECTL**
\ No newline at end of file
diff --git a/resources/README_ZH_TW.md b/resources/README_ZH_TW.md
new file mode 100644
index 00000000..b941c621
--- /dev/null
+++ b/resources/README_ZH_TW.md
@@ -0,0 +1,200 @@
+
+
+
+
+# SecRandom - 公平隨機抽取系統
+
+🚀 **現代化教育工具** | 🎯 **智能權重演算法** | 🎨 **優雅互動體驗**
+
+[简体中文](../README.md) | [English](./README_EN.md) | **✔繁體中文**
+
+> 本 Readme **由 AI 翻譯**,並由我們的開發人員審核。如果您發現任何錯誤,請向我們報告。
+
+
+
+
+
+[](https://github.com/SECTL/SecRandom/issues)
+[](https://github.com/SECTL/SecRandom/releases/latest)
+[](https://github.com/SECTL/SecRandom/releases/)
+[](https://github.com/SECTL/SecRandom/commits/master)
+[](https://github.com/SECTL/SecRandom/releases)
+[](https://qm.qq.com/q/iWcfaPHn7W)
+[](https://space.bilibili.com/520571577)
+[](https://opensource.org/licenses/GPL-3.0)
+
+
+
+
+
+> [!note]
+>
+> SecRandom 本體將基於GNU GPLv3協議開源
+>
+> GNU GPLv3具有Copyleft特性,也就是說,您可以修改SecRandom的原始碼,但是**必須將修改版本同樣以GNU GPLv3協議開源**
+---------
+> [!note]
+>
+> **SecRandom v2** 將會在 2025/12/14 (GMT +8:00 中國標準時間) 左右 發布!
+>
+> 敬請關注我們的 BiliBili、QQ 頻道中的內容,我們會不定期發布開發進展等資訊!
+
+## 📖 目錄
+
+- [🌟 核心亮點](#-核心亮點)
+- [📥 下載](#-下載)
+- [📸 軟體截圖](#-軟體截圖)
+- [📖 公平抽取](#-公平抽取)
+- [🙏 貢獻者](#-貢獻者和特別感謝)
+- [💝 捐獻支持](#-捐獻支持)
+- [📞 聯絡方式](#-聯絡方式)
+
+## 🌟 核心亮點
+
+### 🎯 **智能公平抽取系統**
+
+- ✅ **動態權重演算法**:基於抽取次數、小組、性別等多維度計算,確保真正的公平性
+- ✅ **冷啟動保護**:防止新成員權重過低,保證每個人都有平等機會
+- ✅ **機率可視化**:直觀展示每個成員被抽中的機率,讓抽取過程透明化
+
+### 🎨 **現代化使用者體驗**
+
+- ✅ **優雅UI設計**:基於 Fluent Design 的現代化介面,支援淺色/深色主題
+- ✅ **懸浮窗模式**:可隨時進行抽取,不影響其他工作
+- ✅ **語音播報**:抽取結果自動語音播報,支援自訂語音引擎
+
+### 🚀 **強大功能集**
+
+- ✅ **多種抽取模式**:單人/多人/小組/性別抽取,滿足不同場景需求
+- ✅ **智慧歷史記錄**:帶時間戳的詳細記錄,支援自動清理
+- ✅ **多名單管理**:支援匯入/匯出名單,輕鬆管理不同班級/團隊
+
+### 💻 **系統相容性**
+
+- ✅ **全平台支援**:完美相容 Windows 7/10/11 系統
+- ✅ **多架構適配**:原生支援 x64、x86 架構
+- ✅ **開機自啟**:支援開機自動啟動,隨時可用
+
+## 📥 下載
+
+### 🌐 官方下載頁面
+
+- 📥 **[官方下載頁面](https://secrandom.netlify.app/download)** - 獲取最新穩定版本和測試版本
+
+### 📦 下載源選擇
+
+#### 官方渠道
+
+- **GitHub 官方源** - 官方發布渠道,中國大陸外訪問較快,推薦使用
+- **123雲盤源** - 雲盤下載,不限速,在中國大陸訪問較快,適合大檔案下載
+
+#### 中國大陸加速鏡像
+
+> [!note]
+>
+> 這些鏡像主要為中國大陸用戶提供。
+
+- **GitHub 鏡像源(ghfast.top)** - 加速鏡像,速度快且穩定
+- **GitHub 鏡像源(gh-proxy.com)** - 加速鏡像,適合網路環境特殊的使用者
+
+## 📸 軟體截圖
+
+> [!warning]
+>
+> 以下的截圖均為 **中文簡體** 語言版本。
+
+
+📸 軟體截圖展示 ✨
+
+
+
+
+
+
+
+
+## 📖 公平抽取
+
+### 簡介
+公平抽取是一種隨機抽取方式,它確保每個成員被抽取的權重由系統決定,從而避免不公平的結果。
+這種方式適用於需要隨機且公平的抽取學生回答問題或進行其他需要公平分配的場景。
+SecRandom的公平抽取的實作基於動態權重系統,透過多個方面來進行權重的計算。
+
+### **動態權重系統**
+
+動態權重是SecRandom的公平抽取的核心機制。
+它透過以下幾個方面來計算每個成員的權重:
+
+1. **總抽取次數**:被抽中次數越多權重越低,避免重複抽取
+2. **抽取各小組次數**:平衡不同小組的抽取機會
+3. **抽取各性別次數**:確保性別平衡
+4. **基礎權重**:可自訂的初始權重設定
+5. **冷啟動保護**:防止新成員權重過低,保證公平性
+
+## 建構與打包
+
+### 觸發建構
+在提交資訊中包含 `進行打包` 即可觸發自動建構流程。
+
+
+
+## 🙏 貢獻者和特別感謝
+
+
+
+
+
+
+## 💝 捐獻支持
+
+如果您覺得 SecRandom 對您有幫助,歡迎支持我們的開發工作!
+
+- **支付寶/微信支付**
+
+
+- **爱发电**
+[為黎泽懿_Aionflux(黎澤懿_Aionflux)發電](https://afdian.com/a/lzy0983)
+
+## 📞 聯絡方式
+
+* 📧 [郵箱](mailto:lzy.12@foxmail.com)
+* 👥 [QQ群 833875216](https://qm.qq.com/q/iWcfaPHn7W)
+* 💬 [QQ頻道](https://pd.qq.com/s/4x5dafd34?b=9)
+* 🎥 [B站主頁](https://space.bilibili.com/520571577)
+* 🐛 [問題回饋](https://github.com/SECTL/SecRandom/issues)
+
+## 📄 官方文檔
+
+- 📄 **[SecRandom 官方文檔](https://secrandom.netlify.app)**
+- [](https://deepwiki.com/SECTL/SecRandom)
+
+
+## 🏆 貢獻值
+
+>[!TIP]
+>
+> 📊 **貢獻值計算公式**:貢獻值 = 文檔提交新增行數 x5 + 主程式提交新增行數 x5 + 處理issue x5
+>
+> 📅 **統計時間範圍**:2025.08.01 - 2026.01.31 (中國時間 UTC+8)
+>
+> 🏗️ **統計倉庫**:SECTL/SecRandom, SECTL/SecRandom-docs
+
+## ✨ Star歷程
+
+
+
+
+
+
+**Copyright © 2025 SECTL**
\ No newline at end of file
diff --git "a/resources/ScreenShots/\344\270\273\347\225\214\351\235\242_\346\212\275\344\272\272_\346\265\205\350\211\262.png" "b/resources/ScreenShots/\344\270\273\347\225\214\351\235\242_\346\212\275\344\272\272_\346\265\205\350\211\262.png"
new file mode 100644
index 00000000..e1f8fdc1
Binary files /dev/null and "b/resources/ScreenShots/\344\270\273\347\225\214\351\235\242_\346\212\275\344\272\272_\346\265\205\350\211\262.png" differ
diff --git "a/resources/ScreenShots/\344\270\273\347\225\214\351\235\242_\346\212\275\344\272\272\345\216\206\345\217\262\350\256\260\345\275\225_\346\265\205\350\211\262.png" "b/resources/ScreenShots/\344\270\273\347\225\214\351\235\242_\346\212\275\344\272\272\345\216\206\345\217\262\350\256\260\345\275\225_\346\265\205\350\211\262.png"
new file mode 100644
index 00000000..dcddbd21
Binary files /dev/null and "b/resources/ScreenShots/\344\270\273\347\225\214\351\235\242_\346\212\275\344\272\272\345\216\206\345\217\262\350\256\260\345\275\225_\346\265\205\350\211\262.png" differ
diff --git "a/resources/ScreenShots/\344\270\273\347\225\214\351\235\242_\346\212\275\345\245\226_\346\265\205\350\211\262.png" "b/resources/ScreenShots/\344\270\273\347\225\214\351\235\242_\346\212\275\345\245\226_\346\265\205\350\211\262.png"
new file mode 100644
index 00000000..441908b5
Binary files /dev/null and "b/resources/ScreenShots/\344\270\273\347\225\214\351\235\242_\346\212\275\345\245\226_\346\265\205\350\211\262.png" differ
diff --git "a/resources/ScreenShots/\350\256\276\347\275\256_\346\212\275\344\272\272\350\256\276\347\275\256_\346\265\205\350\211\262.png" "b/resources/ScreenShots/\350\256\276\347\275\256_\346\212\275\344\272\272\350\256\276\347\275\256_\346\265\205\350\211\262.png"
new file mode 100644
index 00000000..23376f7e
Binary files /dev/null and "b/resources/ScreenShots/\350\256\276\347\275\256_\346\212\275\344\272\272\350\256\276\347\275\256_\346\265\205\350\211\262.png" differ
diff --git a/test_combobox_fix.py b/test_combobox_fix.py
new file mode 100644
index 00000000..e69de29b
diff --git a/test_packaging.py b/test_packaging.py
new file mode 100644
index 00000000..f527cb9e
--- /dev/null
+++ b/test_packaging.py
@@ -0,0 +1,182 @@
+"""
+打包验证测试脚本
+用于验证修复后的打包是否正常工作
+"""
+
+import sys
+
+
+def test_imports():
+ """测试关键模块导入"""
+ print("=" * 60)
+ print("测试 1: 模块导入")
+ print("=" * 60)
+
+ try:
+ from app.tools.path_utils import get_path, get_app_root # noqa: F401
+
+ print("✓ path_utils 导入成功")
+
+ from app.tools.language_manager import get_current_language_data # noqa: F401
+
+ print("✓ language_manager 导入成功")
+
+ from app.Language.obtain_language import Language # noqa: F401
+
+ print("✓ obtain_language 导入成功")
+
+ return True
+ except Exception as e:
+ print(f"✗ 导入失败: {e}")
+ return False
+
+
+def test_paths():
+ """测试路径获取"""
+ print("\n" + "=" * 60)
+ print("测试 2: 路径获取")
+ print("=" * 60)
+
+ try:
+ from app.tools.path_utils import get_app_root, get_path
+
+ app_root = get_app_root()
+ print(f"应用根目录: {app_root}")
+ print(f"是否为打包环境: {getattr(sys, 'frozen', False)}")
+
+ if hasattr(sys, "_MEIPASS"):
+ print(f"PyInstaller 临时目录: {sys._MEIPASS}")
+
+ # 测试资源路径
+ resources_path = get_path("app/resources")
+ print(f"资源目录: {resources_path}")
+ print(f"资源目录存在: {resources_path.exists()}")
+
+ # 测试语言模块路径
+ lang_modules_path = get_path("app/Language/modules")
+ print(f"语言模块目录: {lang_modules_path}")
+ print(f"语言模块目录存在: {lang_modules_path.exists()}")
+
+ return True
+ except Exception as e:
+ print(f"✗ 路径测试失败: {e}")
+ import traceback
+
+ traceback.print_exc()
+ return False
+
+
+def test_language_loading():
+ """测试语言加载"""
+ print("\n" + "=" * 60)
+ print("测试 3: 语言数据加载")
+ print("=" * 60)
+
+ try:
+ from app.tools.language_manager import get_current_language_data
+
+ lang_data = get_current_language_data()
+ print(f"语言数据类型: {type(lang_data)}")
+ print(
+ f"语言数据键数量: {len(lang_data.keys()) if isinstance(lang_data, dict) else 'N/A'}"
+ )
+
+ if isinstance(lang_data, dict) and len(lang_data) > 0:
+ # 显示前几个键
+ keys = list(lang_data.keys())[:5]
+ print(f"语言数据示例键: {keys}")
+ print("✓ 语言数据加载成功")
+ return True
+ else:
+ print("✗ 语言数据为空")
+ return False
+
+ except Exception as e:
+ print(f"✗ 语言加载失败: {e}")
+ import traceback
+
+ traceback.print_exc()
+ return False
+
+
+def test_resource_files():
+ """测试资源文件存在性"""
+ print("\n" + "=" * 60)
+ print("测试 4: 资源文件检查")
+ print("=" * 60)
+
+ try:
+ from app.tools.path_utils import get_path
+
+ # 检查关键资源目录
+ resource_dirs = [
+ "app/resources/assets",
+ "app/resources/font",
+ "app/resources/Language",
+ "app/Language/modules",
+ ]
+
+ all_exist = True
+ for dir_path in resource_dirs:
+ path = get_path(dir_path)
+ exists = path.exists()
+ status = "✓" if exists else "✗"
+ print(f"{status} {dir_path}: {exists}")
+ if not exists:
+ all_exist = False
+
+ return all_exist
+ except Exception as e:
+ print(f"✗ 资源文件检查失败: {e}")
+ import traceback
+
+ traceback.print_exc()
+ return False
+
+
+def main():
+ """主测试函数"""
+ print("\n" + "=" * 60)
+ print("SecRandom 打包验证测试")
+ print("=" * 60 + "\n")
+
+ results = []
+
+ # 运行测试
+ results.append(("模块导入", test_imports()))
+ results.append(("路径获取", test_paths()))
+ results.append(("语言加载", test_language_loading()))
+ results.append(("资源文件", test_resource_files()))
+
+ # 汇总结果
+ print("\n" + "=" * 60)
+ print("测试结果汇总")
+ print("=" * 60)
+
+ passed = 0
+ failed = 0
+
+ for name, result in results:
+ status = "通过" if result else "失败"
+ symbol = "✓" if result else "✗"
+ print(f"{symbol} {name}: {status}")
+
+ if result:
+ passed += 1
+ else:
+ failed += 1
+
+ print("\n" + "=" * 60)
+ print(f"总计: {passed} 通过, {failed} 失败")
+ print("=" * 60 + "\n")
+
+ if failed == 0:
+ print("🎉 所有测试通过!打包修复成功!")
+ return 0
+ else:
+ print("⚠️ 部分测试失败,请检查错误信息")
+ return 1
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/update_version.py b/update_version.py
index fee4f875..bb0ff0d0 100644
--- a/update_version.py
+++ b/update_version.py
@@ -1,39 +1,59 @@
import os
import re
+
def get_version_from_env():
- original_version = os.getenv('VERSION', 'v0.0.0.0')
- stripped_version = re.sub(r'^v', '', original_version)
- numeric_version = re.search(r'^(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.(\d+))?', stripped_version)
+ original_version = os.getenv("VERSION", "v0.0.0.0")
+ stripped_version = re.sub(r"^v", "", original_version)
+ numeric_version = re.search(
+ r"^(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.(\d+))?", stripped_version
+ )
if numeric_version:
- parts = numeric_version.groups(default='0')
- stripped_version = '.'.join(parts)
+ parts = numeric_version.groups(default="0")
+ stripped_version = ".".join(parts)
else:
- stripped_version = '0.0.0.0'
- original_version = 'v0.0.0.0'
+ stripped_version = "0.0.0.0"
+ original_version = "v0.0.0.0"
return original_version, stripped_version
+
def update_version_info(version):
- major, minor, patch, build = map(int, version.split('.'))
+ major, minor, patch, build = map(int, version.split("."))
version_tuple = (major, minor, patch, build)
version_str = f"{major}.{minor}.{patch}.{build}"
- with open('version_info.txt', 'r', encoding='utf-8') as f:
+ with open("version_info.txt", "r", encoding="utf-8") as f:
content = f.read()
- content = re.sub(r'filevers=\(\d, \d, \d, \d\)', f'filevers={version_tuple}', content)
- content = re.sub(r'prodvers=\(\d, \d, \d, \d\)', f'prodvers={version_tuple}', content)
- content = re.sub(r'StringStruct\(u\'FileVersion\', u\'\d+\.\d+\.\d+\.\d+\'\)', f'StringStruct(u\'FileVersion\', u\'{version_str}\')', content)
- content = re.sub(r'StringStruct\(u\'ProductVersion\', u\'\d+\.\d+\.\d+\.\d+\'\)', f'StringStruct(u\'ProductVersion\', u\'{version_str}\')', content)
- with open('version_info.txt', 'w', encoding='utf-8') as f:
+ content = re.sub(
+ r"filevers=\(\d, \d, \d, \d\)", f"filevers={version_tuple}", content
+ )
+ content = re.sub(
+ r"prodvers=\(\d, \d, \d, \d\)", f"prodvers={version_tuple}", content
+ )
+ content = re.sub(
+ r"StringStruct\(u\'FileVersion\', u\'\d+\.\d+\.\d+\.\d+\'\)",
+ f"StringStruct(u'FileVersion', u'{version_str}')",
+ content,
+ )
+ content = re.sub(
+ r"StringStruct\(u\'ProductVersion\', u\'\d+\.\d+\.\d+\.\d+\'\)",
+ f"StringStruct(u'ProductVersion', u'{version_str}')",
+ content,
+ )
+ with open("version_info.txt", "w", encoding="utf-8") as f:
f.write(content)
+
def update_config_py(version):
- with open('app/tools/variable.py', 'r', encoding='utf-8') as f:
+ with open("app/tools/variable.py", "r", encoding="utf-8") as f:
content = f.read()
- content = re.sub(r'VERSION = "v?\d+\.\d+\.\d+\.\d+"', f'VERSION = "{version}"', content)
- with open('app/tools/variable.py', 'w', encoding='utf-8') as f:
+ content = re.sub(
+ r'VERSION = "v?\d+\.\d+\.\d+\.\d+"', f'VERSION = "{version}"', content
+ )
+ with open("app/tools/variable.py", "w", encoding="utf-8") as f:
f.write(content)
-if __name__ == '__main__':
+
+if __name__ == "__main__":
original_version, stripped_version = get_version_from_env()
update_version_info(stripped_version)
- update_config_py(original_version)
\ No newline at end of file
+ update_config_py(original_version)
diff --git a/uv.lock b/uv.lock
index 013f9494..54fe78fb 100644
--- a/uv.lock
+++ b/uv.lock
@@ -1,5 +1,5 @@
version = 1
-revision = 2
+revision = 3
requires-python = "==3.8.10"
[[package]]
@@ -132,6 +132,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/2f/70/80c33b044ebc79527447fd4fbc5455d514c3bb840dede4455de97da39b4d/cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1", size = 181229, upload-time = "2024-09-04T20:44:59.963Z" },
]
+[[package]]
+name = "cfgv"
+version = "3.4.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload-time = "2023-08-12T20:38:17.776Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" },
+]
+
[[package]]
name = "charset-normalizer"
version = "3.4.4"
@@ -207,6 +216,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl", hash = "sha256:a7509ccf517eaad92b31c214f593dbcf138ea8a43b2935406bbd565e15527a85", size = 8955, upload-time = "2022-12-16T14:14:40.92Z" },
]
+[[package]]
+name = "distlib"
+version = "0.4.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" },
+]
+
[[package]]
name = "edge-tts"
version = "7.0.2"
@@ -244,6 +262,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" },
]
+[[package]]
+name = "filelock"
+version = "3.16.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/9d/db/3ef5bb276dae18d6ec2124224403d1d67bccdbefc17af4cc8f553e341ab1/filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435", size = 18037, upload-time = "2024-09-17T19:02:01.779Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b9/f8/feced7779d755758a52d1f6635d990b8d98dc0a29fa568bbe0625f18fdf3/filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0", size = 16163, upload-time = "2024-09-17T19:02:00.268Z" },
+]
+
[[package]]
name = "flake8"
version = "7.1.2"
@@ -282,6 +309,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c6/c8/a5be5b7550c10858fcf9b0ea054baccab474da77d37f1e828ce043a3a5d4/frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3", size = 11901, upload-time = "2024-10-23T09:48:28.851Z" },
]
+[[package]]
+name = "identify"
+version = "2.6.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/29/bb/25024dbcc93516c492b75919e76f389bac754a3e4248682fba32b250c880/identify-2.6.1.tar.gz", hash = "sha256:91478c5fb7c3aac5ff7bf9b4344f803843dc586832d5f110d672b19aa1984c98", size = 99097, upload-time = "2024-09-14T23:50:32.513Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7d/0c/4ef72754c050979fdcc06c744715ae70ea37e734816bb6514f79df77a42f/identify-2.6.1-py2.py3-none-any.whl", hash = "sha256:53863bcac7caf8d2ed85bd20312ea5dcfc22226800f6d6881f232d861db5a8f0", size = 98972, upload-time = "2024-09-14T23:50:30.747Z" },
+]
+
[[package]]
name = "idna"
version = "3.11"
@@ -291,6 +327,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
]
+[[package]]
+name = "imageio"
+version = "2.35.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "numpy" },
+ { name = "pillow" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/82/bf/d0ddda79819405428f40e4bc9245c2b936a3a2b23d83b6e42d83822ef822/imageio-2.35.1.tar.gz", hash = "sha256:4952dfeef3c3947957f6d5dedb1f4ca31c6e509a476891062396834048aeed2a", size = 389686, upload-time = "2024-08-19T02:35:27.783Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/1e/b7/02adac4e42a691008b5cfb31db98c190e1fc348d1521b9be4429f9454ed1/imageio-2.35.1-py3-none-any.whl", hash = "sha256:6eb2e5244e7a16b85c10b5c2fe0f7bf961b40fcb9f1a9fd1bd1d2c2f8fb3cd65", size = 315378, upload-time = "2024-08-19T02:35:25.923Z" },
+]
+
[[package]]
name = "iniconfig"
version = "2.1.0"
@@ -400,6 +449,25 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
]
+[[package]]
+name = "nodeenv"
+version = "1.9.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" },
+]
+
+[[package]]
+name = "nuitka"
+version = "2.8.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "ordered-set" },
+ { name = "zstandard" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/6f/87/f20ffda1b6dc04361fa95390f4d47d974ee194e6e1e7688f13d324f3d89b/Nuitka-2.8.4.tar.gz", hash = "sha256:06b020ef33be97194f888dcfcd4c69c8452ceb61b31c7622e610d5156eb7923d", size = 3885111, upload-time = "2025-10-21T10:28:45.499Z" }
+
[[package]]
name = "numpy"
version = "1.24.4"
@@ -429,6 +497,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c0/da/977ded879c29cbd04de313843e76868e6e13408a94ed6b987245dc7c8506/openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", size = 250910, upload-time = "2024-06-28T14:03:41.161Z" },
]
+[[package]]
+name = "ordered-set"
+version = "4.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/4c/ca/bfac8bc689799bcca4157e0e0ced07e70ce125193fc2e166d2e685b7e2fe/ordered-set-4.1.0.tar.gz", hash = "sha256:694a8e44c87657c59292ede72891eb91d34131f6531463aab3009191c77364a8", size = 12826, upload-time = "2022-01-26T14:38:56.6Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/33/55/af02708f230eb77084a299d7b08175cff006dea4f2721074b92cdb0296c0/ordered_set-4.1.0-py3-none-any.whl", hash = "sha256:046e1132c71fcf3330438a539928932caf51ddbc582496833e23de611de14562", size = 7634, upload-time = "2022-01-26T14:38:48.677Z" },
+]
+
[[package]]
name = "packaging"
version = "25.0"
@@ -503,6 +580,22 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" },
]
+[[package]]
+name = "pre-commit"
+version = "3.5.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "cfgv" },
+ { name = "identify" },
+ { name = "nodeenv" },
+ { name = "pyyaml" },
+ { name = "virtualenv" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/04/b3/4ae08d21eb097162f5aad37f4585f8069a86402ed7f5362cc9ae097f9572/pre_commit-3.5.0.tar.gz", hash = "sha256:5804465c675b659b0862f07907f96295d490822a450c4c40e747d0b1c6ebcb32", size = 177079, upload-time = "2023-10-13T15:57:48.334Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/6c/75/526915fedf462e05eeb1c75ceaf7e3f9cde7b5ce6f62740fe5f7f19a0050/pre_commit-3.5.0-py2.py3-none-any.whl", hash = "sha256:841dc9aef25daba9a0238cd27984041fa0467b4199fc4852e27950664919f660", size = 203698, upload-time = "2023-10-13T15:57:46.378Z" },
+]
+
[[package]]
name = "propcache"
version = "0.2.0"
@@ -543,6 +636,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" },
]
+[[package]]
+name = "pulsectl"
+version = "24.8.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/38/fa/114007b76cfae16624dc2ec31e293c805825907515eb8c10e2643b3ffed9/pulsectl-24.8.0.tar.gz", hash = "sha256:b051506d0d73d3cc4357cefd3de17bb859d7ecf004e994b0f7cfa87851bc7156", size = 41171, upload-time = "2024-08-27T01:09:16.501Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/87/17/fcb5d0bd9179704689c8fa7d5656ebebe168a24187f14be3b4fa9f697fe9/pulsectl-24.8.0-py2.py3-none-any.whl", hash = "sha256:050ae7e122c51a55f47c2ce0b377ebbcbc86e163514048904b5ba580ab6912c5", size = 35110, upload-time = "2024-08-27T01:09:14.722Z" },
+]
+
[[package]]
name = "pycaw"
version = "20240210"
@@ -3143,73 +3245,89 @@ source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/37/61/f07226075c347897937d4086ef8e55f0a62ae535e28069884ac68d979316/PyQRCode-1.2.1.tar.gz", hash = "sha256:fdbf7634733e56b72e27f9bce46e4550b75a3a2c420414035cae9d9d26b234d5", size = 36989, upload-time = "2016-06-20T03:28:03.411Z" }
[[package]]
-name = "pyqt6"
-version = "6.7.1"
+name = "pyright"
+version = "1.1.407"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyqt6-qt6" },
- { name = "pyqt6-sip" },
+ { name = "nodeenv" },
+ { name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/d1/f9/b0c2ba758b14a7219e076138ea1e738c068bf388e64eee68f3df4fc96f5a/PyQt6-6.7.1.tar.gz", hash = "sha256:3672a82ccd3a62e99ab200a13903421e2928e399fda25ced98d140313ad59cb9", size = 1051212, upload-time = "2024-07-19T08:49:58.247Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/a6/1b/0aa08ee42948b61745ac5b5b5ccaec4669e8884b53d31c8ec20b2fcd6b6f/pyright-1.1.407.tar.gz", hash = "sha256:099674dba5c10489832d4a4b2d302636152a9a42d317986c38474c76fe562262", size = 4122872, upload-time = "2025-10-24T23:17:15.145Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/45/b0/20a05cfe287a1bc5a034cfed002bb1999f71c15e53a6ab7886c010ea0ba3/PyQt6-6.7.1-1-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7f397f4b38b23b5588eb2c0933510deb953d96b1f0323a916c4839c2a66ccccc", size = 8020146, upload-time = "2024-07-31T09:50:04.992Z" },
- { url = "https://files.pythonhosted.org/packages/e4/d3/8789879c05cfe06127c4b59258632bd175fcdd9eaaadaf0c897b458fb91d/PyQt6-6.7.1-1-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2f202b7941aa74e5c7e1463a6f27d9131dbc1e6cabe85571d7364f5b3de7397", size = 8227345, upload-time = "2024-07-31T09:50:13.511Z" },
- { url = "https://files.pythonhosted.org/packages/15/2b/a0c516931697214dcb93b24a62f54b7467194ba1c76f3f7a55cb3a120cc9/PyQt6-6.7.1-cp38-abi3-macosx_11_0_universal2.whl", hash = "sha256:f053378e3aef6248fa612c8afddda17f942fb63f9fe8a9aeb2a6b6b4cbb0eba9", size = 11871174, upload-time = "2024-07-19T08:49:21.831Z" },
- { url = "https://files.pythonhosted.org/packages/59/8c/3b528f5fa8dfc3d0ba07d8da37ea72dfc59352d80804a12507d7080efb30/PyQt6-6.7.1-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0adb7914c732ad1dee46d9cec838a98cb2b11bc38cc3b7b36fbd8701ae64bf47", size = 7999939, upload-time = "2024-07-19T08:49:30.82Z" },
- { url = "https://files.pythonhosted.org/packages/d8/58/5082dd3654da2b17de19057f181526df566f38af90f517cb8a541bea0890/PyQt6-6.7.1-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2d771fa0981514cb1ee937633dfa64f14caa902707d9afffab66677f3a73e3da", size = 8177790, upload-time = "2024-07-19T08:49:48.394Z" },
- { url = "https://files.pythonhosted.org/packages/a3/69/99d22ee685c08a99fcf2048d366fe6173ba6e43ee13b95a3a2ac2911c52c/PyQt6-6.7.1-cp38-abi3-win_amd64.whl", hash = "sha256:fa3954698233fe286a8afc477b84d8517f0788eb46b74da69d3ccc0170d3714c", size = 6596360, upload-time = "2024-07-19T08:49:55.594Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/93/b69052907d032b00c40cb656d21438ec00b3a471733de137a3f65a49a0a0/pyright-1.1.407-py3-none-any.whl", hash = "sha256:6dd419f54fcc13f03b52285796d65e639786373f433e243f8b94cf93a7444d21", size = 5997008, upload-time = "2025-10-24T23:17:13.159Z" },
]
[[package]]
-name = "pyqt6-fluent-widgets"
-version = "1.9.1"
+name = "pyside6"
+version = "6.6.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "darkdetect" },
- { name = "pyqt6" },
- { name = "pyqt6-frameless-window" },
+ { name = "pyside6-addons" },
+ { name = "pyside6-essentials" },
+ { name = "shiboken6" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/d3/c2/e8c97a77b0dad640595e36df4fea01fb166cb40baff2ebe81e9bf357daef/pyqt6_fluent_widgets-1.9.1.tar.gz", hash = "sha256:6dcb11808ac5379a29b07b2568888ba1bb2f6f6298f35a50d370925224d3f614", size = 1443395, upload-time = "2025-10-12T09:50:07.471Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/f4/8d/15aa1bf90a7dce7864f46df0c29e35f665d8a49231f1aab34b453dcfe85a/pyqt6_fluent_widgets-1.9.1-py3-none-any.whl", hash = "sha256:eb26fd2e9e78827160e9d156d4ae58dd1fab3bbd4f324ce87db74434cd9b3f63", size = 1537576, upload-time = "2025-10-12T09:50:05.296Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/9a/3483d05305701ba810192572cee5977ff884c033a1b8f96ab9582d81ccd4/PySide6-6.6.3.1-cp38-abi3-macosx_11_0_universal2.whl", hash = "sha256:3d2ebb08a7744b59e1270e57f264a9ef5b45fccdc0328a9aeb50d890d6b3f4f2", size = 512759, upload-time = "2024-04-02T12:28:14.771Z" },
+ { url = "https://files.pythonhosted.org/packages/14/60/dc79d4ea59ed1ebe6062c5db972b31d489ea84315dcf3bd58a2a741c73b3/PySide6-6.6.3.1-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:35936f06257e5c37ae8993da0cb5a528e5db3ea1fc2bb6b12cdf899a11510966", size = 513325, upload-time = "2024-04-02T12:28:17.925Z" },
+ { url = "https://files.pythonhosted.org/packages/51/3e/b77d2b9a1efcb5c90a2df4f51eb10bce45b3787c4fa16b69c599fd6620b9/PySide6-6.6.3.1-cp38-abi3-manylinux_2_31_aarch64.whl", hash = "sha256:f7acd26fe8e1a745ef0be66b49ee49ee8ae50c2a2855d9792db262ebc7916d98", size = 513326, upload-time = "2024-04-02T12:28:20.418Z" },
+ { url = "https://files.pythonhosted.org/packages/af/90/3164ace42cb80ed55642e965934133d0c49bfa3ea79e43631dd331cdc866/PySide6-6.6.3.1-cp38-abi3-win_amd64.whl", hash = "sha256:d993989a10725c856f5b07f25e0664c5059daa92c259549c9df0972b5b0c7935", size = 520559, upload-time = "2024-04-02T12:28:23.459Z" },
]
[[package]]
-name = "pyqt6-frameless-window"
-version = "0.7.4"
+name = "pyside6-addons"
+version = "6.6.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pycocoa", marker = "sys_platform == 'darwin'" },
- { name = "pyobjc", marker = "sys_platform == 'darwin'" },
- { name = "pywin32", marker = "sys_platform == 'win32'" },
+ { name = "pyside6-essentials" },
+ { name = "shiboken6" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/db/ab/f40b5ae9fed90268e46d7d3efc35db0bbd144c30685a230e2768a720f069/pyqt6_frameless_window-0.7.4.tar.gz", hash = "sha256:eab1dbe2b90451bf9680bed107913e4fef8286b71fe19936f651efb5838b3f03", size = 32486, upload-time = "2025-10-10T13:14:58.128Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/76/01/feb88e4de9571ff3b6bcaacfd02fb4f22ff5467371cfaeb85aed0ea74a75/pyqt6_frameless_window-0.7.4-py3-none-any.whl", hash = "sha256:a0806e459d40ab607fe035efa2cee27a1dced35641eb5e42c0b8527832bd8565", size = 40029, upload-time = "2025-10-10T13:14:54.079Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/43/b4d9264969552450c2e889450908279302360901b530f3ec3eb1154db5bf/PySide6_Addons-6.6.3.1-cp38-abi3-macosx_11_0_universal2.whl", hash = "sha256:31135adc521ed6e3fdc8203507e7e9d72424d6b9ebd245d1189d991e90669d6a", size = 250159667, upload-time = "2024-04-02T12:08:03.498Z" },
+ { url = "https://files.pythonhosted.org/packages/02/fc/e265aa0c338ddd8a4f2c3526aadc58f60980508ac56999ba79cf2ce744a7/PySide6_Addons-6.6.3.1-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7373479565e5bd963b9662857c40c20768bc0b5853334e2076a62cb039e91f74", size = 125913866, upload-time = "2024-04-02T12:09:59.877Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/52/d56c3380f300b14f26be8eaf98af71a128e7e7952a2b3f4c8b24b1547e0a/PySide6_Addons-6.6.3.1-cp38-abi3-manylinux_2_31_aarch64.whl", hash = "sha256:3abdc1e21de0c6763e5392af5ed8b2349291318ce235e7c310d84a2f9d5001a9", size = 111711166, upload-time = "2024-04-02T12:11:17.038Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/c6/fc354ab30ca87b34fa62794e75a65a6b8bc7f4e858c5fd217b8706a143bb/PySide6_Addons-6.6.3.1-cp38-abi3-win_amd64.whl", hash = "sha256:d8fbcd726dbf3e713e5d5ccc45ff0e1a9edfe336d7190c96cf7e7c7598681239", size = 111743197, upload-time = "2024-04-02T12:12:29.83Z" },
]
[[package]]
-name = "pyqt6-qt6"
-version = "6.7.3"
+name = "pyside6-essentials"
+version = "6.6.3.1"
source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "shiboken6" },
+]
wheels = [
- { url = "https://files.pythonhosted.org/packages/25/a2/9ef7c001068da2d3c8c37fe0e1e0451b1073d47c6ef4e44abf5883559963/PyQt6_Qt6-6.7.3-py3-none-macosx_10_14_x86_64.whl", hash = "sha256:f517a93b6b1a814d4aa6587adc312e812ebaf4d70415bb15cfb44268c5ad3f5f", size = 49136114, upload-time = "2024-09-29T16:25:15.055Z" },
- { url = "https://files.pythonhosted.org/packages/ec/63/a85bdd7c66800208f0af417bb4d07cb1543a75384021e4594e66d919f855/PyQt6_Qt6-6.7.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8551732984fb36a5f4f3db51eafc4e8e6caf18617365830285306f2db17a94c2", size = 45762813, upload-time = "2024-09-29T16:25:20.956Z" },
- { url = "https://files.pythonhosted.org/packages/8a/6c/4f329f83a6082a7b4c1dc6046e2c48edb72e0d6d0ca3f8d0701fe134dccf/PyQt6_Qt6-6.7.3-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:50c7482bcdcf2bb78af257fb10ed8b582f8daf91d829782393bc50ac5a0a900c", size = 63801442, upload-time = "2024-09-29T16:25:26.637Z" },
- { url = "https://files.pythonhosted.org/packages/88/4d/26ca7239f7223e5b95b58a58537a09b069582ebb4dfa38234113a9f898ab/PyQt6_Qt6-6.7.3-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:cb525fdd393332de60887953029276a44de480fce1d785251ae639580f5e7246", size = 74366973, upload-time = "2024-09-29T16:25:33.595Z" },
- { url = "https://files.pythonhosted.org/packages/7e/57/3b44f6af1020fa543bd564c5bd346ba4aab1f1be0b861c2e8a0ad88cf3ca/PyQt6_Qt6-6.7.3-py3-none-win_amd64.whl", hash = "sha256:36ea0892b8caeb983af3f285f45fb8dfbb93cfd972439f4e01b7efb2868f6230", size = 58467498, upload-time = "2024-09-29T16:25:39.569Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/47/69e1c0dd4305a30e01e54257fe08d7719da0464b1e2bd351d23831c0018c/PySide6_Essentials-6.6.3.1-cp38-abi3-macosx_11_0_universal2.whl", hash = "sha256:6c16530b63079711783796584b640cc80a347e0b2dc12651aa2877265df7a008", size = 147274572, upload-time = "2024-04-02T12:20:59.741Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/29/2375cccf188862c3297f40cb06832cd48fd98fd5da73b0b296a59f54c9f4/PySide6_Essentials-6.6.3.1-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1f41f357ce2384576581e76c9c3df1c4fa5b38e347f0bcd0cae7c5bce42a917c", size = 82521622, upload-time = "2024-04-02T12:22:11.01Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/2c/d6c6102a1a803f0619932996fed59c90429a09850a2b8c19f44f92dd4189/PySide6_Essentials-6.6.3.1-cp38-abi3-manylinux_2_31_aarch64.whl", hash = "sha256:27034525fdbdd21ef21f20fcd7aaf5c2ffe26f2bcf5269a69dd9492dec7e92aa", size = 66881609, upload-time = "2024-04-02T12:23:15.977Z" },
+ { url = "https://files.pythonhosted.org/packages/03/c2/d4e78dd7661889b97e52fbfed908ce65abf1422dc03cc7e90752b52ff1f5/PySide6_Essentials-6.6.3.1-cp38-abi3-win_amd64.whl", hash = "sha256:31f7e70ada44d3cdbe6686670b3df036c720cfeb1dced0f7704e5f5a4be6a764", size = 77264921, upload-time = "2024-04-02T12:24:22.734Z" },
]
[[package]]
-name = "pyqt6-sip"
-version = "13.8.0"
+name = "pyside6-fluent-widgets"
+version = "1.9.1"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/e9/b7/95ac49b181096ef40144ef05aff8de7c9657de7916a70533d202ed9f0fd2/PyQt6_sip-13.8.0.tar.gz", hash = "sha256:2f74cf3d6d9cab5152bd9f49d570b2dfb87553ebb5c4919abfde27f5b9fd69d4", size = 92264, upload-time = "2024-07-12T15:55:14.173Z" }
+dependencies = [
+ { name = "darkdetect" },
+ { name = "pyside6" },
+ { name = "pysidesix-frameless-window" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/52/37/6522f7c9926e212204036d9cca467b9c2151ed9d4987b4f0f701c91a750a/pyside6_fluent_widgets-1.9.1.tar.gz", hash = "sha256:dc64be855b422ecba63893e71dc5b9753e3196da7e3bcf355e2796cf90c6cfac", size = 1442742, upload-time = "2025-10-12T09:50:09.6Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/02/73/3770655726f5dce7a2074bf93308627515cc59950de92f585f704923746e/PyQt6_sip-13.8.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:6bce6bc5870d9e87efe5338b1ee4a7b9d7d26cdd16a79a5757d80b6f25e71edc", size = 110868, upload-time = "2024-07-12T15:54:57.849Z" },
- { url = "https://files.pythonhosted.org/packages/f2/f3/74dbbdb6a7b9925f5d9a61c87f17aa3f77cb3b9b178de0367f1e1d30e420/PyQt6_sip-13.8.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd81144b0770084e8005d3a121c9382e6f9bc8d0bb320dd618718ffe5090e0e6", size = 291648, upload-time = "2024-07-12T15:55:01.374Z" },
- { url = "https://files.pythonhosted.org/packages/08/c2/ce68821747e44dd5ea8b5383fbba4dbb645035f39baf84ef06fd8d9f34a9/PyQt6_sip-13.8.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:755beb5d271d081e56618fb30342cdd901464f721450495cb7cb0212764da89e", size = 282922, upload-time = "2024-07-12T15:55:03.776Z" },
- { url = "https://files.pythonhosted.org/packages/33/29/daf492243ce000143a8bb3c9f84b7387e35f738254637790fa216a77c609/PyQt6_sip-13.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:7a0bbc0918eab5b6351735d40cf22cbfa5aa2476b55e0d5fe881aeed7d871c29", size = 53452, upload-time = "2024-07-12T15:55:05.309Z" },
+ { url = "https://files.pythonhosted.org/packages/72/d5/8326585b83ea7eb0e18eb1e7025fcd853ff63832ef4aec3719c489253e81/pyside6_fluent_widgets-1.9.1-py3-none-any.whl", hash = "sha256:9c56ba8fcdcdca2a388cb11871101ba642d085a2717c43b64f935ec23c6b1c69", size = 1536206, upload-time = "2025-10-12T09:50:05.921Z" },
+]
+
+[[package]]
+name = "pysidesix-frameless-window"
+version = "0.7.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pycocoa", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc", marker = "sys_platform == 'darwin'" },
+ { name = "pywin32", marker = "sys_platform == 'win32'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/be/f2/dde32565af5ae10841f4288a22f71debb6bf529b6b8149f629042f31e557/pysidesix_frameless_window-0.7.4.tar.gz", hash = "sha256:1b14ee5bf5438d6f5823e9a108a9d3c04c096f7db733b75f7e91e009684b0be0", size = 22771, upload-time = "2025-10-10T13:17:32.024Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3d/1e/5bfb71bb258d7fe5b6a89197ae1d8b318ea6895ba5e5b2cdabed0956b07d/pysidesix_frameless_window-0.7.4-py3-none-any.whl", hash = "sha256:26eca5f28e1e08ff8e619de6b8a8c19aa85fe3c07efc2bf05693015430c7ea68", size = 30595, upload-time = "2025-10-10T13:17:30.885Z" },
]
[[package]]
@@ -3274,6 +3392,21 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/0c/3b/05f848971b3a44b35cd48ea0c6c648745be8bc5a3fc9f4df6f135c7f1e07/pywin32-310-cp38-cp38-win_amd64.whl", hash = "sha256:30f0a9b3138fb5e07eb4973b7077e1883f558e40c578c6925acc7a94c34eaa36", size = 9609017, upload-time = "2025-03-17T00:55:40.483Z" },
]
+[[package]]
+name = "pyyaml"
+version = "6.0.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0d/a2/09f67a3589cb4320fb5ce90d3fd4c9752636b8b6ad8f34b54d76c5a54693/PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f", size = 186824, upload-time = "2025-09-29T20:27:35.918Z" },
+ { url = "https://files.pythonhosted.org/packages/02/72/d972384252432d57f248767556ac083793292a4adf4e2d85dfe785ec2659/PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4", size = 795069, upload-time = "2025-09-29T20:27:38.15Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/3b/6c58ac0fa7c4e1b35e48024eb03d00817438310447f93ef4431673c24138/PyYAML-6.0.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3", size = 862585, upload-time = "2025-09-29T20:27:39.715Z" },
+ { url = "https://files.pythonhosted.org/packages/25/a2/b725b61ac76a75583ae7104b3209f75ea44b13cfd026aa535ece22b7f22e/PyYAML-6.0.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6", size = 806018, upload-time = "2025-09-29T20:27:41.444Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/b0/b2227677b2d1036d84f5ee95eb948e7af53d59fe3e4328784e4d290607e0/PyYAML-6.0.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369", size = 802822, upload-time = "2025-09-29T20:27:42.885Z" },
+ { url = "https://files.pythonhosted.org/packages/99/a5/718a8ea22521e06ef19f91945766a892c5ceb1855df6adbde67d997ea7ed/PyYAML-6.0.3-cp38-cp38-win32.whl", hash = "sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295", size = 143744, upload-time = "2025-09-29T20:27:44.487Z" },
+ { url = "https://files.pythonhosted.org/packages/76/b2/2b69cee94c9eb215216fc05778675c393e3aa541131dc910df8e52c83776/PyYAML-6.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b", size = 160082, upload-time = "2025-09-29T20:27:46.049Z" },
+]
+
[[package]]
name = "requests"
version = "2.32.4"
@@ -3289,6 +3422,32 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" },
]
+[[package]]
+name = "ruff"
+version = "0.14.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/75/62/50b7727004dfe361104dfbf898c45a9a2fdfad8c72c04ae62900224d6ecf/ruff-0.14.3.tar.gz", hash = "sha256:4ff876d2ab2b161b6de0aa1f5bd714e8e9b4033dc122ee006925fbacc4f62153", size = 5558687, upload-time = "2025-10-31T00:26:26.878Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ce/8e/0c10ff1ea5d4360ab8bfca4cb2c9d979101a391f3e79d2616c9bf348cd26/ruff-0.14.3-py3-none-linux_armv6l.whl", hash = "sha256:876b21e6c824f519446715c1342b8e60f97f93264012de9d8d10314f8a79c371", size = 12535613, upload-time = "2025-10-31T00:25:44.302Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/c8/6724f4634c1daf52409fbf13fefda64aa9c8f81e44727a378b7b73dc590b/ruff-0.14.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b6fd8c79b457bedd2abf2702b9b472147cd860ed7855c73a5247fa55c9117654", size = 12855812, upload-time = "2025-10-31T00:25:47.793Z" },
+ { url = "https://files.pythonhosted.org/packages/de/03/db1bce591d55fd5f8a08bb02517fa0b5097b2ccabd4ea1ee29aa72b67d96/ruff-0.14.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:71ff6edca490c308f083156938c0c1a66907151263c4abdcb588602c6e696a14", size = 11944026, upload-time = "2025-10-31T00:25:49.657Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/75/4f8dbd48e03272715d12c87dc4fcaaf21b913f0affa5f12a4e9c6f8a0582/ruff-0.14.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:786ee3ce6139772ff9272aaf43296d975c0217ee1b97538a98171bf0d21f87ed", size = 12356818, upload-time = "2025-10-31T00:25:51.949Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/9b/506ec5b140c11d44a9a4f284ea7c14ebf6f8b01e6e8917734a3325bff787/ruff-0.14.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cd6291d0061811c52b8e392f946889916757610d45d004e41140d81fb6cd5ddc", size = 12336745, upload-time = "2025-10-31T00:25:54.248Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/e1/c560d254048c147f35e7f8131d30bc1f63a008ac61595cf3078a3e93533d/ruff-0.14.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a497ec0c3d2c88561b6d90f9c29f5ae68221ac00d471f306fa21fa4264ce5fcd", size = 13101684, upload-time = "2025-10-31T00:25:56.253Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/32/e310133f8af5cd11f8cc30f52522a3ebccc5ea5bff4b492f94faceaca7a8/ruff-0.14.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e231e1be58fc568950a04fbe6887c8e4b85310e7889727e2b81db205c45059eb", size = 14535000, upload-time = "2025-10-31T00:25:58.397Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/a1/7b0470a22158c6d8501eabc5e9b6043c99bede40fa1994cadf6b5c2a61c7/ruff-0.14.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:469e35872a09c0e45fecf48dd960bfbce056b5db2d5e6b50eca329b4f853ae20", size = 14156450, upload-time = "2025-10-31T00:26:00.889Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/96/24bfd9d1a7f532b560dcee1a87096332e461354d3882124219bcaff65c09/ruff-0.14.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d6bc90307c469cb9d28b7cfad90aaa600b10d67c6e22026869f585e1e8a2db0", size = 13568414, upload-time = "2025-10-31T00:26:03.291Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/e7/138b883f0dfe4ad5b76b58bf4ae675f4d2176ac2b24bdd81b4d966b28c61/ruff-0.14.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2f8a0bbcffcfd895df39c9a4ecd59bb80dca03dc43f7fb63e647ed176b741e", size = 13315293, upload-time = "2025-10-31T00:26:05.708Z" },
+ { url = "https://files.pythonhosted.org/packages/33/f4/c09bb898be97b2eb18476b7c950df8815ef14cf956074177e9fbd40b7719/ruff-0.14.3-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:678fdd7c7d2d94851597c23ee6336d25f9930b460b55f8598e011b57c74fd8c5", size = 13539444, upload-time = "2025-10-31T00:26:08.09Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/aa/b30a1db25fc6128b1dd6ff0741fa4abf969ded161599d07ca7edd0739cc0/ruff-0.14.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1ec1ac071e7e37e0221d2f2dbaf90897a988c531a8592a6a5959f0603a1ecf5e", size = 12252581, upload-time = "2025-10-31T00:26:10.297Z" },
+ { url = "https://files.pythonhosted.org/packages/da/13/21096308f384d796ffe3f2960b17054110a9c3828d223ca540c2b7cc670b/ruff-0.14.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:afcdc4b5335ef440d19e7df9e8ae2ad9f749352190e96d481dc501b753f0733e", size = 12307503, upload-time = "2025-10-31T00:26:12.646Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/cc/a350bac23f03b7dbcde3c81b154706e80c6f16b06ff1ce28ed07dc7b07b0/ruff-0.14.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:7bfc42f81862749a7136267a343990f865e71fe2f99cf8d2958f684d23ce3dfa", size = 12675457, upload-time = "2025-10-31T00:26:15.044Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/76/46346029fa2f2078826bc88ef7167e8c198e58fe3126636e52f77488cbba/ruff-0.14.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a65e448cfd7e9c59fae8cf37f9221585d3354febaad9a07f29158af1528e165f", size = 13403980, upload-time = "2025-10-31T00:26:17.81Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/a4/35f1ef68c4e7b236d4a5204e3669efdeefaef21f0ff6a456792b3d8be438/ruff-0.14.3-py3-none-win32.whl", hash = "sha256:f3d91857d023ba93e14ed2d462ab62c3428f9bbf2b4fbac50a03ca66d31991f7", size = 12500045, upload-time = "2025-10-31T00:26:20.503Z" },
+ { url = "https://files.pythonhosted.org/packages/03/15/51960ae340823c9859fb60c63301d977308735403e2134e17d1d2858c7fb/ruff-0.14.3-py3-none-win_amd64.whl", hash = "sha256:d7b7006ac0756306db212fd37116cce2bd307e1e109375e1c6c106002df0ae5f", size = 13594005, upload-time = "2025-10-31T00:26:22.533Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/73/4de6579bac8e979fca0a77e54dec1f1e011a0d268165eb8a9bc0982a6564/ruff-0.14.3-py3-none-win_arm64.whl", hash = "sha256:26eb477ede6d399d898791d01961e16b86f02bc2486d0d1a7a9bb2379d055dc1", size = 12590017, upload-time = "2025-10-31T00:26:24.52Z" },
+]
+
[[package]]
name = "secrandom"
version = "1.1.0"
@@ -3297,28 +3456,30 @@ dependencies = [
{ name = "asyncio" },
{ name = "colorama" },
{ name = "colorthief" },
- { name = "comtypes" },
+ { name = "comtypes", marker = "sys_platform == 'win32'" },
{ name = "darkdetect" },
{ name = "edge-tts" },
+ { name = "imageio" },
{ name = "jinja2" },
{ name = "keyboard" },
{ name = "loguru" },
+ { name = "nuitka" },
{ name = "numpy" },
{ name = "openpyxl" },
{ name = "packaging" },
{ name = "pandas" },
{ name = "pillow" },
+ { name = "pre-commit" },
{ name = "psutil" },
+ { name = "pulsectl", marker = "sys_platform == 'linux'" },
{ name = "pycaw", marker = "sys_platform == 'win32'" },
{ name = "pycryptodome" },
{ name = "pyotp" },
{ name = "pypng" },
{ name = "pyqrcode" },
- { name = "pyqt6" },
- { name = "pyqt6-fluent-widgets" },
- { name = "pyqt6-frameless-window" },
- { name = "pyqt6-qt6" },
- { name = "pyqt6-sip" },
+ { name = "pyside6" },
+ { name = "pyside6-fluent-widgets" },
+ { name = "pysidesix-frameless-window" },
{ name = "pyttsx3" },
{ name = "pywin32", marker = "sys_platform == 'win32'" },
{ name = "requests" },
@@ -3327,7 +3488,7 @@ dependencies = [
{ name = "soundfile" },
{ name = "win32-setctime", marker = "sys_platform == 'win32'" },
{ name = "winshell", marker = "sys_platform == 'win32'" },
- { name = "wmi" },
+ { name = "wmi", marker = "sys_platform == 'win32'" },
]
[package.optional-dependencies]
@@ -3341,7 +3502,10 @@ dev = [
dev = [
{ name = "black" },
{ name = "flake8" },
+ { name = "pre-commit" },
+ { name = "pyright" },
{ name = "pytest" },
+ { name = "ruff" },
]
[package.metadata]
@@ -3350,29 +3514,31 @@ requires-dist = [
{ name = "black", marker = "extra == 'dev'" },
{ name = "colorama", specifier = "==0.4.6" },
{ name = "colorthief", specifier = "==0.2.1" },
- { name = "comtypes", specifier = "==1.4.10" },
+ { name = "comtypes", marker = "sys_platform == 'win32'", specifier = "==1.4.10" },
{ name = "darkdetect", specifier = "==0.8.0" },
{ name = "edge-tts", specifier = "==7.0.2" },
{ name = "flake8", marker = "extra == 'dev'" },
+ { name = "imageio", specifier = ">=2.35.1" },
{ name = "jinja2", specifier = "~=3.1.6" },
{ name = "keyboard", specifier = "==0.13.5" },
{ name = "loguru", specifier = "==0.7.3" },
+ { name = "nuitka", specifier = ">=2.8.4" },
{ name = "numpy", specifier = "==1.24.4" },
{ name = "openpyxl", specifier = "==3.1.5" },
{ name = "packaging", specifier = "==25.0" },
{ name = "pandas", specifier = "~=2.0.3" },
{ name = "pillow", specifier = "~=10.4.0" },
+ { name = "pre-commit", specifier = ">=3.5.0" },
{ name = "psutil", specifier = "~=7.0.0" },
+ { name = "pulsectl", marker = "sys_platform == 'linux'", specifier = "==24.8.0" },
{ name = "pycaw", marker = "sys_platform == 'win32'", specifier = "==20240210" },
{ name = "pycryptodome", specifier = "==3.23.0" },
{ name = "pyotp", specifier = "==2.9.0" },
{ name = "pypng", specifier = "~=0.20220715.0" },
{ name = "pyqrcode", specifier = "~=1.2.1" },
- { name = "pyqt6", specifier = "==6.7.1" },
- { name = "pyqt6-fluent-widgets", specifier = "==1.9.1" },
- { name = "pyqt6-frameless-window", specifier = "==0.7.4" },
- { name = "pyqt6-qt6", specifier = "==6.7.3" },
- { name = "pyqt6-sip", specifier = "==13.8.0" },
+ { name = "pyside6", specifier = ">=6.6.3.1" },
+ { name = "pyside6-fluent-widgets", specifier = "==1.9.1" },
+ { name = "pysidesix-frameless-window", specifier = ">=0.7.4" },
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=6.0" },
{ name = "pyttsx3", specifier = "==2.98" },
{ name = "pywin32", marker = "sys_platform == 'win32'", specifier = "==310" },
@@ -3382,7 +3548,7 @@ requires-dist = [
{ name = "soundfile", specifier = "==0.13.1" },
{ name = "win32-setctime", marker = "sys_platform == 'win32'", specifier = "==1.2.0" },
{ name = "winshell", marker = "sys_platform == 'win32'", specifier = "==0.6" },
- { name = "wmi", specifier = "==1.5.1" },
+ { name = "wmi", marker = "sys_platform == 'win32'", specifier = "==1.5.1" },
]
provides-extras = ["dev"]
@@ -3390,7 +3556,10 @@ provides-extras = ["dev"]
dev = [
{ name = "black" },
{ name = "flake8" },
+ { name = "pre-commit", specifier = ">=3.5.0" },
+ { name = "pyright", specifier = ">=1.1.407" },
{ name = "pytest", specifier = ">=6.0" },
+ { name = "ruff", specifier = ">=0.14.3" },
]
[[package]]
@@ -3402,6 +3571,17 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/15/65/3f0dba35760d902849d39d38c0a72767794b1963227b69a587f8a336d08c/setuptools-75.3.2-py3-none-any.whl", hash = "sha256:90ab613b6583fc02d5369cbca13ea26ea0e182d1df2d943ee9cbe81d4c61add9", size = 1251198, upload-time = "2025-03-12T00:02:17.554Z" },
]
+[[package]]
+name = "shiboken6"
+version = "6.6.3.1"
+source = { registry = "https://pypi.org/simple" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/af/fb/183b7889168f44b19526f58c571b88e23375150abcbd5b603dd3a288ef7a/shiboken6-6.6.3.1-cp38-abi3-macosx_11_0_universal2.whl", hash = "sha256:2a8df586aa9eb629388b368d3157893083c5217ed3eb637bf182d1948c823a0f", size = 345925, upload-time = "2024-04-02T12:24:42.21Z" },
+ { url = "https://files.pythonhosted.org/packages/77/f1/feb2a8be699f91fb27fbe8758b405fb38a22e3ae5bd5e05258dbef18d462/shiboken6-6.6.3.1-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b1aeff0d79d84ddbdc9970144c1bbc3a52fcb45618d1b33d17d57f99f1246d45", size = 171474, upload-time = "2024-04-02T12:24:44.914Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/03/e71f0f3fc35fcc90265d1345e3afa509bbd2d6bb305c6e78427a9b27efea/shiboken6-6.6.3.1-cp38-abi3-manylinux_2_31_aarch64.whl", hash = "sha256:902d9e126ac57cc3841cdc50ba38d53948b40cf667538172f253c4ae7b2dcb2c", size = 162427, upload-time = "2024-04-02T12:24:46.669Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/f3/b4153287806a63ee064570ed527f16f9eacedab4f4ea99cb84b55e624e21/shiboken6-6.6.3.1-cp38-abi3-win_amd64.whl", hash = "sha256:88494b5e08a1f235efddbe2b0b225a3a66e07d72b6091fcc2fc5448572453649", size = 1068203, upload-time = "2024-04-02T12:24:50.09Z" },
+]
+
[[package]]
name = "sip"
version = "6.8.6"
@@ -3510,6 +3690,21 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", size = 126338, upload-time = "2024-09-12T10:52:16.589Z" },
]
+[[package]]
+name = "virtualenv"
+version = "20.35.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "distlib" },
+ { name = "filelock" },
+ { name = "platformdirs" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/20/28/e6f1a6f655d620846bd9df527390ecc26b3805a0c5989048c210e22c5ca9/virtualenv-20.35.4.tar.gz", hash = "sha256:643d3914d73d3eeb0c552cbb12d7e82adf0e504dbf86a3182f8771a153a1971c", size = 6028799, upload-time = "2025-10-29T06:57:40.511Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl", hash = "sha256:c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b", size = 6005095, upload-time = "2025-10-29T06:57:37.598Z" },
+]
+
[[package]]
name = "win32-setctime"
version = "1.2.0"
@@ -3566,3 +3761,30 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/9c/9d/d944e897abf37f50f4fa2d8d6f5fd0ed9413bc8327d3b4cc25ba9694e1ba/yarl-1.15.2-cp38-cp38-win_amd64.whl", hash = "sha256:2cf441c4b6e538ba0d2591574f95d3fdd33f1efafa864faa077d9636ecc0c4e9", size = 84998, upload-time = "2024-10-13T18:47:23.301Z" },
{ url = "https://files.pythonhosted.org/packages/46/cf/a28c494decc9c8776b0d7b729c68d26fdafefcedd8d2eab5d9cd767376b2/yarl-1.15.2-py3-none-any.whl", hash = "sha256:0d3105efab7c5c091609abacad33afff33bdff0035bece164c98bcf5a85ef90a", size = 38891, upload-time = "2024-10-13T18:48:00.883Z" },
]
+
+[[package]]
+name = "zstandard"
+version = "0.23.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "cffi", marker = "platform_python_implementation == 'PyPy'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ed/f6/2ac0287b442160a89d726b17a9184a4c615bb5237db763791a7fd16d9df1/zstandard-0.23.0.tar.gz", hash = "sha256:b2d8c62d08e7255f68f7a740bae85b3c9b8e5466baa9cbf7f57f1cde0ac6bc09", size = 681701, upload-time = "2024-07-15T00:18:06.141Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/fb/96/867dd4f5e9ee6215f83985c43f4134b28c058617a7af8ad9592669f960dd/zstandard-0.23.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2ef3775758346d9ac6214123887d25c7061c92afe1f2b354f9388e9e4d48acfc", size = 788685, upload-time = "2024-07-15T00:16:54.954Z" },
+ { url = "https://files.pythonhosted.org/packages/19/57/e81579db7740757036e97dc461f4f26a318fe8dfc6b3477dd557b7f85aae/zstandard-0.23.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4051e406288b8cdbb993798b9a45c59a4896b6ecee2f875424ec10276a895740", size = 633665, upload-time = "2024-07-15T00:16:56.665Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/a5/b8c9d79511796684a2a653843e0464dfcc11a052abb5855af7035d919ecc/zstandard-0.23.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2d1a054f8f0a191004675755448d12be47fa9bebbcffa3cdf01db19f2d30a54", size = 4944817, upload-time = "2024-07-15T00:16:59.183Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/59/ee5a3c4f060c431d3aaa7ff2b435d9723c579bffda274d071c981bf08b17/zstandard-0.23.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f83fa6cae3fff8e98691248c9320356971b59678a17f20656a9e59cd32cee6d8", size = 5311485, upload-time = "2024-07-15T00:17:02.046Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/70/ea438a09d757d49c5bb73a895c13492277b83981c08ed294441b1965eaf2/zstandard-0.23.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32ba3b5ccde2d581b1e6aa952c836a6291e8435d788f656fe5976445865ae045", size = 5340843, upload-time = "2024-07-15T00:17:04.526Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/4b/be9f3f9ed33ff4d5e578cf167c16ac1d8542232d5e4831c49b615b5918a6/zstandard-0.23.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f146f50723defec2975fb7e388ae3a024eb7151542d1599527ec2aa9cacb152", size = 5442446, upload-time = "2024-07-15T00:17:06.672Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/17/55eff9df9004e1896f2ade19981e7cd24d06b463fe72f9a61f112b8185d0/zstandard-0.23.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1bfe8de1da6d104f15a60d4a8a768288f66aa953bbe00d027398b93fb9680b26", size = 4863800, upload-time = "2024-07-15T00:17:08.685Z" },
+ { url = "https://files.pythonhosted.org/packages/59/8c/fe542982e63e1948066bf2adc18e902196eb08f3407188474b5a4e855e2e/zstandard-0.23.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:29a2bc7c1b09b0af938b7a8343174b987ae021705acabcbae560166567f5a8db", size = 4935488, upload-time = "2024-07-15T00:17:10.942Z" },
+ { url = "https://files.pythonhosted.org/packages/38/6c/a54e30864aff0cc065c053fbdb581114328f70f45f30fcb0f80b12bb4460/zstandard-0.23.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:61f89436cbfede4bc4e91b4397eaa3e2108ebe96d05e93d6ccc95ab5714be512", size = 5467670, upload-time = "2024-07-15T00:17:13.115Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/11/32788cc80aa8c1069a9fdc48a60355bd25ac8211b2414dd0ff6ee6bb5ff5/zstandard-0.23.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:53ea7cdc96c6eb56e76bb06894bcfb5dfa93b7adcf59d61c6b92674e24e2dd5e", size = 4859904, upload-time = "2024-07-15T00:17:15.637Z" },
+ { url = "https://files.pythonhosted.org/packages/60/93/baf7ad86b2258c08c06bdccdaddeb3d6d0918601e16fa9c73c8079c8c816/zstandard-0.23.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:a4ae99c57668ca1e78597d8b06d5af837f377f340f4cce993b551b2d7731778d", size = 4700723, upload-time = "2024-07-15T00:17:17.889Z" },
+ { url = "https://files.pythonhosted.org/packages/95/bd/e65f1c1e0185ed0c7f5bda51b0d73fc379a75f5dc2583aac83dd131378dc/zstandard-0.23.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:379b378ae694ba78cef921581ebd420c938936a153ded602c4fea612b7eaa90d", size = 5208667, upload-time = "2024-07-15T00:17:21.032Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/cf/2dfa4610829c6c1dbc3ce858caed6de13928bec78c1e4d0bedfd4b20589b/zstandard-0.23.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:50a80baba0285386f97ea36239855f6020ce452456605f262b2d33ac35c7770b", size = 5667083, upload-time = "2024-07-15T00:17:23.441Z" },
+ { url = "https://files.pythonhosted.org/packages/16/f6/d84d95984fb9c8f57747ffeff66677f0a58acf430f9ddff84bc3b9aad35d/zstandard-0.23.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:61062387ad820c654b6a6b5f0b94484fa19515e0c5116faf29f41a6bc91ded6e", size = 5195874, upload-time = "2024-07-15T00:17:25.5Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/a6/239f43f2e3ea0360c5641c075bd587c7f2a32b29d9ba53a538435621bcbb/zstandard-0.23.0-cp38-cp38-win32.whl", hash = "sha256:b8c0bd73aeac689beacd4e7667d48c299f61b959475cdbb91e7d3d88d27c56b9", size = 430654, upload-time = "2024-07-15T00:17:27.687Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/b6/16e737301831c9c62379ed466c3d916c56b8a9a95fbce9bf1d7fea318945/zstandard-0.23.0-cp38-cp38-win_amd64.whl", hash = "sha256:a05e6d6218461eb1b4771d973728f0133b2a4613a6779995df557f70794fd60f", size = 495519, upload-time = "2024-07-15T00:17:29.553Z" },
+]