Skip to content

Commit 85ca05d

Browse files
committed
安全设置中新增预览设置功能
- 新增 预览设置开关 ,在 安全设置页面 新增控制是否在打开设置的验证过程中显示预览按钮的功能 - 优化 验证窗口显示逻辑 , 预览按钮 只在 打开设置 操作时显示,其他验证场景不显示预览按钮 - 修复 预览设置开关稳定性 ,解决 开关操作 可能导致 软件卡退 的问题,改进 UI状态 处理逻辑避免闪烁 - 优化 预览功能 , 预览设置开关 默认为 开启状态 ,用户可控制是否在验证过程中显示预览按钮
1 parent 7eba1a1 commit 85ca05d

12 files changed

Lines changed: 524 additions & 94 deletions

File tree

CHANGELOG/v1.3.2-alpha.6/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ v2.0 - Koharu(小鸟游星野) Alpha 6
1212
- 新增 **闪抽点击后禁用功能**,新增**点击后禁用时间设置**,支持设置**0-60s**的禁用时间,防止频繁点击
1313
- 新增 **通知窗口字体设置**,新增通知窗口支持独立的字体设置功能
1414
- 新增 **自动保存窗口大小设置**,新增设置是否自动保存窗口大小功能
15+
- 新增 **预览设置开关**,在**安全设置页面**新增控制是否在打开设置的验证过程中显示预览按钮的功能
1516

1617
## 💡 功能优化
1718

@@ -27,6 +28,8 @@ v2.0 - Koharu(小鸟游星野) Alpha 6
2728
- 优化 **阈值设置文本**,将**主窗口显示阈值**改为**浮窗通知阈值**,并更新描述为"设置触发浮窗通知的人数/奖数阈值,超过此数值则不显示浮窗通知",使用户更容易理解功能作用
2829
- 优化 **通知窗口字体应用**,优化字体设置应用方式,将字体设置正确应用到标签组件而非布局对象
2930
- 优化 **通知窗口字体同步**,优化通知窗口根据类型使用相应功能的字体设置,确保与主界面保持一致
31+
- 优化 **预览功能****预览设置开关**默认为**开启状态**,用户可控制是否在验证过程中显示预览按钮
32+
- 优化 **验证窗口显示逻辑****预览按钮**只在**打开设置**操作时显示,其他验证场景不显示预览按钮
3033

3134
## 🐛 修复问题
3235

@@ -48,6 +51,7 @@ v2.0 - Koharu(小鸟游星野) Alpha 6
4851
- 修复 **文件写入权限**,新增使用**临时文件**策略避免secrets.json写入时的权限拒绝问题
4952
- 修复 **U盘验证布局**,优化仅在需要**U盘验证**时才显示相关控件(状态标签和刷新按钮)
5053
- 修复 **安全验证流程**,新增验证通过后**刷新下拉框**显示状态的功能
54+
- 修复 **预览设置开关稳定性**,解决**开关操作**可能导致**软件卡退**的问题,改进**UI状态**处理逻辑避免闪烁
5155

5256
## 🔧 其它变更
5357

app/Language/modules/safety_settings.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,12 @@
155155
"description": "启用后导入版本不匹配时需验证",
156156
"switchbutton_name": {"enable": "", "disable": ""},
157157
},
158+
"preview_settings": {"name": "预览设置"},
159+
"preview_settings_switch": {
160+
"name": "设置预览开关",
161+
"description": "进入设置的验证时允许预览设置",
162+
"switchbutton_name": {"enable": "", "disable": ""},
163+
},
158164
},
159165
"EN_US": {
160166
"title": {
@@ -309,5 +315,11 @@
309315
"name": "Version mismatch import validation",
310316
"description": "Authentication is required for importing versions when they do not match",
311317
},
318+
"preview_settings": {"name": "Preview settings"},
319+
"preview_settings_switch": {
320+
"name": "Preview settings switch",
321+
"description": "Enable preview settings switch",
322+
"switchbutton_name": {"enable": "", "disable": ""},
323+
},
312324
},
313325
}

app/common/safety/verify_ops.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,11 @@ def should_require_password(op: str) -> bool:
5555
return ok
5656

5757

58-
def require_and_run(op: str, parent, func):
58+
def require_and_run(op: str, parent, func, on_preview=None):
5959
if not should_require_password(op):
6060
func()
6161
return
6262
logger.debug(f"触发验证窗口:{op}")
63-
create_verify_password_window(on_verified=func)
63+
create_verify_password_window(
64+
on_verified=func, on_preview=on_preview, operation_type=op
65+
)

app/page_building/page_template.py

Lines changed: 172 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,49 @@ def __new__(cls, content_widget_class=None, parent: QFrame = None, **kwargs):
2525
# 直接创建新实例,不使用缓存
2626
return super(PageTemplate, cls).__new__(cls)
2727

28-
def __init__(self, content_widget_class=None, parent: QFrame = None, **kwargs):
28+
def __init__(
29+
self,
30+
content_widget_class=None,
31+
parent: QFrame = None,
32+
is_preview_mode=False,
33+
**kwargs,
34+
):
2935
super().__init__(parent=parent)
3036

3137
self.ui_created = False
3238
self.content_created = False
3339
self.content_widget_class = content_widget_class
40+
self._is_preview_mode = is_preview_mode
3441

3542
self.__connectSignalToSlot()
3643
self.create_ui_components()
3744

45+
@property
46+
def is_preview_mode(self):
47+
"""获取是否为预览模式"""
48+
return self._is_preview_mode
49+
50+
@is_preview_mode.setter
51+
def is_preview_mode(self, value):
52+
"""设置是否为预览模式,并在值改变时触发锁定逻辑
53+
54+
Args:
55+
value: 是否为预览模式
56+
"""
57+
self._is_preview_mode = value
58+
# 如果内容组件已经创建完成,立即执行锁定逻辑
59+
if (
60+
self.content_created
61+
and hasattr(self, "contentWidget")
62+
and self.contentWidget
63+
):
64+
if value:
65+
self._lock_all_widgets(self.contentWidget)
66+
else:
67+
# 如果不是预览模式,可以选择解锁所有组件
68+
# 但通常情况下,预览模式是一次性的,所以这里可以不处理
69+
pass
70+
3871
def __connectSignalToSlot(self):
3972
qconfig.themeChanged.connect(setTheme)
4073

@@ -86,7 +119,7 @@ def _ensure_scroll_area(self):
86119

87120
def create_content(self):
88121
"""后台创建内容组件,避免堵塞进程 - 使用延迟创建的布局"""
89-
if not self.ui_created or self.content_created or not self.content_widget_class:
122+
if not self.ui_created or not self.content_widget_class:
90123
return
91124

92125
# 确保滚动区域已创建
@@ -113,10 +146,25 @@ def create_content(self):
113146
content_cls = self.content_widget_class
114147
content_name = getattr(content_cls, "__name__", str(content_cls))
115148

116-
# 实例化并添加到延迟创建的布局
117-
self.contentWidget = content_cls(self)
118-
self._inner_layout_lazy.addWidget(self.contentWidget)
119-
self.content_created = True
149+
# 如果内容组件尚未创建,创建并添加到布局
150+
if not self.content_created:
151+
# 实例化并添加到延迟创建的布局
152+
self.contentWidget = content_cls(self)
153+
self._inner_layout_lazy.addWidget(self.contentWidget)
154+
self.content_created = True
155+
156+
# 如果是预览模式,立即锁定所有组件
157+
if self.is_preview_mode:
158+
self._lock_all_widgets(self.contentWidget)
159+
else:
160+
# 如果内容组件已经创建,检查是否需要更新锁定状态
161+
if hasattr(self, "contentWidget") and self.contentWidget:
162+
if self.is_preview_mode:
163+
self._lock_all_widgets(self.contentWidget)
164+
else:
165+
# 如果不是预览模式,可以选择解锁所有组件
166+
# 但通常情况下,预览模式是一次性的,所以这里可以不处理
167+
pass
120168

121169
elapsed = time.perf_counter() - start
122170
loguru.logger.debug(f"创建内容组件 {content_name} 耗时: {elapsed:.3f}s")
@@ -154,7 +202,51 @@ def create_empty_content(self, message="该页面正在开发中,敬请期待
154202
self.inner_layout_personal.addWidget(self.contentWidget)
155203
self.content_created = True
156204

157-
@classmethod
205+
def _lock_all_widgets(self, widget):
206+
"""递归锁定所有子组件,包括嵌套的组件和GroupHeaderCardWidget内部的控件
207+
208+
Args:
209+
widget: 要锁定的根组件
210+
"""
211+
if widget is None:
212+
return
213+
214+
# 首先锁定当前组件
215+
if hasattr(widget, "setEnabled"):
216+
widget.setEnabled(False)
217+
218+
# 特殊处理GroupHeaderCardWidget,确保锁定所有addItem或addGroup添加的控件
219+
try:
220+
if hasattr(widget, "widgets"):
221+
# 如果GroupHeaderCardWidget有widgets属性,锁定所有widgets
222+
for w in widget.widgets:
223+
if isinstance(w, QWidget):
224+
self._lock_all_widgets(w)
225+
elif hasattr(widget, "items"):
226+
# 如果有items属性,锁定所有items
227+
for item in widget.items:
228+
if hasattr(item, "widget") and isinstance(item.widget, QWidget):
229+
self._lock_all_widgets(item.widget)
230+
elif isinstance(item, QWidget):
231+
self._lock_all_widgets(item)
232+
except Exception as e:
233+
logger.debug(f"处理特殊组件时出错: {e}")
234+
235+
# 然后递归锁定所有直接子组件
236+
for child in widget.children():
237+
if isinstance(child, QWidget):
238+
self._lock_all_widgets(child)
239+
240+
# 最后,检查是否有contentWidget或其他可能包含控件的属性
241+
if hasattr(widget, "contentWidget") and isinstance(
242+
widget.contentWidget, QWidget
243+
):
244+
self._lock_all_widgets(widget.contentWidget)
245+
if hasattr(widget, "centralWidget") and isinstance(
246+
widget.centralWidget, QWidget
247+
):
248+
self._lock_all_widgets(widget.centralWidget)
249+
158250
def clear_instance_cache(cls):
159251
"""清除实例缓存,用于强制重新创建页面"""
160252
cls._instances.clear()
@@ -186,13 +278,14 @@ class PivotPageTemplate(QFrame):
186278
- 支持按需重新加载
187279
"""
188280

189-
def __init__(self, page_config: dict, parent: QFrame = None):
281+
def __init__(self, page_config: dict, parent: QFrame = None, is_preview_mode=False):
190282
"""
191283
初始化 Pivot 页面模板
192284
193285
Args:
194286
page_config: 页面配置字典,格式为 {"page_name": "display_name", ...}
195287
parent: 父窗口
288+
is_preview_mode: 是否为预览模式
196289
"""
197290
super().__init__(parent=parent)
198291

@@ -204,11 +297,33 @@ def __init__(self, page_config: dict, parent: QFrame = None):
204297
self.base_path = "app.view.settings.list_management" # 默认基础路径
205298
self._page_load_order = [] # 页面加载顺序,用于LRU卸载
206299
self.MAX_CACHED_PAGES = MAX_CACHED_PAGES # 最大同时保留在内存中的页面数量
300+
self._is_preview_mode = is_preview_mode
207301

208302
self.__connectSignalToSlot()
209303

210304
QTimer.singleShot(0, self.create_ui_components)
211305

306+
@property
307+
def is_preview_mode(self):
308+
"""获取是否为预览模式"""
309+
return self._is_preview_mode
310+
311+
@is_preview_mode.setter
312+
def is_preview_mode(self, value):
313+
"""设置是否为预览模式,并在值改变时触发锁定逻辑
314+
315+
Args:
316+
value: 是否为预览模式
317+
"""
318+
self._is_preview_mode = value
319+
# 如果已经有加载的页面,立即执行锁定逻辑
320+
if hasattr(self, "page_infos"):
321+
for page_name, info in self.page_infos.items():
322+
if info.get("loaded") and info.get("widget"):
323+
widget = info.get("widget")
324+
if widget:
325+
self._lock_all_widgets(widget)
326+
212327
def __connectSignalToSlot(self):
213328
"""连接信号与槽"""
214329
qconfig.themeChanged.connect(setTheme)
@@ -348,6 +463,10 @@ def _load_page_content(
348463
widget = content_widget_class(self)
349464
widget.setObjectName(page_name)
350465

466+
# 如果是预览模式,锁定所有组件
467+
if self.is_preview_mode:
468+
self._lock_all_widgets(widget)
469+
351470
# 清除加载提示(使用安全的 takeAt 循环以避免 Qt C++ 对象提前删除问题)
352471
try:
353472
while inner_layout.count() > 0:
@@ -534,6 +653,51 @@ def get_current_page(self) -> str:
534653
"""获取当前页面名称"""
535654
return self.current_page
536655

656+
def _lock_all_widgets(self, widget):
657+
"""递归锁定所有子组件,包括嵌套的组件和GroupHeaderCardWidget内部的控件
658+
659+
Args:
660+
widget: 要锁定的根组件
661+
"""
662+
if widget is None:
663+
return
664+
665+
# 首先锁定当前组件
666+
if hasattr(widget, "setEnabled"):
667+
widget.setEnabled(False)
668+
669+
# 特殊处理GroupHeaderCardWidget,确保锁定所有addItem或addGroup添加的控件
670+
try:
671+
if hasattr(widget, "widgets"):
672+
# 如果GroupHeaderCardWidget有widgets属性,锁定所有widgets
673+
for w in widget.widgets:
674+
if isinstance(w, QWidget):
675+
self._lock_all_widgets(w)
676+
elif hasattr(widget, "items"):
677+
# 如果有items属性,锁定所有items
678+
for item in widget.items:
679+
if hasattr(item, "widget") and isinstance(item.widget, QWidget):
680+
self._lock_all_widgets(item.widget)
681+
elif isinstance(item, QWidget):
682+
self._lock_all_widgets(item)
683+
except Exception as e:
684+
logger.debug(f"处理特殊组件时出错: {e}")
685+
686+
# 然后递归锁定所有直接子组件
687+
for child in widget.children():
688+
if isinstance(child, QWidget):
689+
self._lock_all_widgets(child)
690+
691+
# 最后,检查是否有contentWidget或其他可能包含控件的属性
692+
if hasattr(widget, "contentWidget") and isinstance(
693+
widget.contentWidget, QWidget
694+
):
695+
self._lock_all_widgets(widget.contentWidget)
696+
if hasattr(widget, "centralWidget") and isinstance(
697+
widget.centralWidget, QWidget
698+
):
699+
self._lock_all_widgets(widget.centralWidget)
700+
537701
def get_page(self, page_name: str):
538702
"""根据页面名称获取页面组件"""
539703
return self.pages.get(page_name, None)

app/page_building/security_window.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -119,11 +119,17 @@ def create_unbind_usb_window():
119119

120120

121121
class verify_password_window_template(PageTemplate):
122-
def __init__(self, parent=None):
123-
super().__init__(content_widget_class=VerifyPasswordWindow, parent=parent)
122+
def __init__(self, parent=None, operation_type=None):
123+
super().__init__(
124+
content_widget_class=VerifyPasswordWindow,
125+
parent=parent,
126+
operation_type=operation_type,
127+
)
124128

125129

126-
def create_verify_password_window(on_verified=None):
130+
def create_verify_password_window(
131+
on_verified=None, on_preview=None, operation_type=None
132+
):
127133
title = get_content_name_async("basic_safety_settings", "safety_switch")
128134
if "verify_password" in _security_window_instances:
129135
w = _security_window_instances["verify_password"]
@@ -136,18 +142,28 @@ def create_verify_password_window(on_verified=None):
136142
_security_window_instances.pop("verify_password", None)
137143
window = SimpleWindowTemplate(title, width=480, height=240)
138144
page = window.add_page_from_template(
139-
"verify_password", verify_password_window_template
145+
"verify_password",
146+
verify_password_window_template,
147+
operation_type=operation_type,
140148
)
141149
window.switch_to_page("verify_password")
150+
content = getattr(page, "contentWidget", None)
142151
if on_verified:
143152
try:
144-
content = getattr(page, "contentWidget", None)
145153
if content is not None and hasattr(content, "verified"):
146154
content.verified.connect(on_verified)
147155
elif hasattr(page, "verified"):
148156
page.verified.connect(on_verified)
149157
except Exception:
150158
pass
159+
if on_preview:
160+
try:
161+
if content is not None and hasattr(content, "previewRequested"):
162+
content.previewRequested.connect(on_preview)
163+
elif hasattr(page, "previewRequested"):
164+
page.previewRequested.connect(on_preview)
165+
except Exception:
166+
pass
151167
_security_window_instances["verify_password"] = window
152168
window.windowClosed.connect(
153169
lambda: _security_window_instances.pop("verify_password", None)

0 commit comments

Comments
 (0)