Skip to content

Commit bc0a6ac

Browse files
committed
优化&新增&修复 一些体验
1 parent 253cb78 commit bc0a6ac

12 files changed

Lines changed: 398 additions & 33 deletions

File tree

CHANGELOG/v2.2.6/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ v2.3 - Shirako (砂狼白子) beta 1
1010
## 💡 功能优化
1111

1212
- 优化 **备份管理**,恢复备份的对话框文本改为对应内容
13+
- 优化 **备份管理**,上限为0时不限制
1314
- 优化 **结果显示布局**,使用流式布局显示
15+
- 优化 **结果显示**,预计算最终结果减少卡顿
1416
- 优化 **抽取临时记录**,区分点名/抽奖文件名
1517
- 优化 **课前重置**,仅清除抽取临时记录
1618
- 优化 **闪抽设置**,移除清除记录项
@@ -19,9 +21,12 @@ v2.3 - Shirako (砂狼白子) beta 1
1921

2022
## 🐛 修复问题
2123

24+
- 修复 **闪抽结果显示**,动画结束重复抽取
25+
- 修复 **闪抽语音播报**,结果与显示不一致
2226
- 修复 **版本号更新脚本**,修复 DEV_VERSION 被误更新的问题
2327
- 修复 **抽取记录读取**,兼容旧格式
2428
- 修复 **通知浮窗显示**,首帧尺寸与上下间距异常
29+
- 修复 **应用重启**,无法重启问题
2530

2631
## 🔧 其它变更
2732

app/Language/modules/basic_settings.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@
113113
},
114114
"backup_auto_max_count": {
115115
"name": "自动备份上限",
116-
"description": "设置自动备份最多保留数量(个)",
116+
"description": "设置自动备份最多保留数量(个),0为不限制",
117117
},
118118
"backup_last_success": {
119119
"name": "上次成功备份",
@@ -438,7 +438,7 @@
438438
},
439439
"backup_auto_max_count": {
440440
"name": "Backup limit",
441-
"description": "Maximum number of backups to keep",
441+
"description": "Maximum number of backups to keep (0 = unlimited)",
442442
},
443443
"backup_last_success": {
444444
"name": "Last successful backup",

app/common/display/result_display.py

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,30 @@ def display_results_in_grid(result_grid, student_labels, alignment=None):
714714
pass
715715
parent_widget.updateGeometry()
716716

717+
@staticmethod
718+
def collect_grid_widgets(result_grid):
719+
widgets = []
720+
if not hasattr(result_grid, "count") or not hasattr(result_grid, "itemAt"):
721+
return widgets
722+
for i in range(result_grid.count()):
723+
item = result_grid.itemAt(i)
724+
if not item or not hasattr(item, "widget"):
725+
continue
726+
widget = item.widget()
727+
if widget:
728+
widgets.append(widget)
729+
return widgets
730+
731+
@staticmethod
732+
def dispose_widgets(widgets):
733+
if not widgets:
734+
return
735+
for widget in widgets:
736+
if widget:
737+
widget.hide()
738+
widget.setParent(None)
739+
widget.deleteLater()
740+
717741
@staticmethod
718742
def update_grid_labels(result_grid, new_labels, cached_labels):
719743
"""
@@ -725,10 +749,12 @@ def update_grid_labels(result_grid, new_labels, cached_labels):
725749
cached_labels: 缓存的旧标签列表
726750
"""
727751
if not new_labels:
728-
return
752+
return False
729753

730754
count = result_grid.count()
731755
min_count = min(len(new_labels), len(cached_labels))
756+
if min_count == 0:
757+
return False
732758

733759
for i in range(min_count):
734760
old_widget = cached_labels[i]
@@ -738,6 +764,12 @@ def update_grid_labels(result_grid, new_labels, cached_labels):
738764
old_layout = old_widget.layout()
739765
new_layout = new_widget.layout()
740766

767+
if not old_layout or not new_layout:
768+
return False
769+
770+
if old_layout.count() != new_layout.count():
771+
return False
772+
741773
if old_layout and new_layout:
742774
for j in range(min(old_layout.count(), new_layout.count())):
743775
old_item = old_layout.itemAt(j)
@@ -747,11 +779,17 @@ def update_grid_labels(result_grid, new_labels, cached_labels):
747779
old_content = old_item.widget()
748780
new_content = new_item.widget()
749781

782+
if old_content is None or new_content is None:
783+
return False
784+
750785
# 如果内容是容器(带头像的情况),更新其内部组件
751786
if old_content.layout() and new_content.layout():
752787
old_inner_layout = old_content.layout()
753788
new_inner_layout = new_content.layout()
754789

790+
if old_inner_layout.count() != new_inner_layout.count():
791+
return False
792+
755793
for k in range(
756794
min(
757795
old_inner_layout.count(),
@@ -764,6 +802,11 @@ def update_grid_labels(result_grid, new_labels, cached_labels):
764802
if old_inner_item and new_inner_item:
765803
old_inner_widget = old_inner_item.widget()
766804
new_inner_widget = new_inner_item.widget()
805+
if (
806+
old_inner_widget is None
807+
or new_inner_widget is None
808+
):
809+
return False
767810

768811
# 更新文本标签
769812
if isinstance(
@@ -788,15 +831,37 @@ def update_grid_labels(result_grid, new_labels, cached_labels):
788831
new_image = new_inner_widget.getImage()
789832
if old_image != new_image:
790833
old_inner_widget.setImage(new_image)
834+
if hasattr(
835+
old_inner_widget, "setText"
836+
) and hasattr(new_inner_widget, "text"):
837+
old_text = (
838+
old_inner_widget.text()
839+
if hasattr(old_inner_widget, "text")
840+
else ""
841+
)
842+
new_text = new_inner_widget.text()
843+
if old_text != new_text:
844+
old_inner_widget.setText(new_text)
845+
if not isinstance(
846+
old_inner_widget, (BodyLabel, AvatarWidget)
847+
) or not isinstance(
848+
new_inner_widget, (BodyLabel, AvatarWidget)
849+
):
850+
return False
791851

792852
# 如果内容是直接标签(不带头像的情况)
793853
elif isinstance(old_content, BodyLabel) and isinstance(
794854
new_content, BodyLabel
795855
):
796856
old_content.setText(new_content.text())
797857
old_content.setStyleSheet(new_content.styleSheet())
858+
else:
859+
return False
860+
else:
861+
return False
798862

799863
cached_labels[:] = new_labels
864+
return True
800865

801866
@staticmethod
802867
def clear_grid(result_grid, log_debug=False):

app/common/lottery/lottery_manager.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,7 +1005,19 @@ def display_result(widget, selected_students, pool_name):
10051005
show_random=render_settings["show_random"],
10061006
settings_group="lottery_settings",
10071007
)
1008-
ResultDisplayUtils.display_results_in_grid(widget.result_grid, student_labels)
1008+
cached_widgets = ResultDisplayUtils.collect_grid_widgets(widget.result_grid)
1009+
if cached_widgets and len(cached_widgets) == len(student_labels):
1010+
updated = ResultDisplayUtils.update_grid_labels(
1011+
widget.result_grid, student_labels, cached_widgets
1012+
)
1013+
if updated:
1014+
ResultDisplayUtils.dispose_widgets(student_labels)
1015+
else:
1016+
ResultDisplayUtils.display_results_in_grid(
1017+
widget.result_grid, student_labels
1018+
)
1019+
else:
1020+
ResultDisplayUtils.display_results_in_grid(widget.result_grid, student_labels)
10091021

10101022

10111023
def display_result_animated(widget, selected_students, pool_name):
@@ -1025,8 +1037,19 @@ def display_result_animated(widget, selected_students, pool_name):
10251037
show_random=render_settings["show_random"],
10261038
settings_group="lottery_settings",
10271039
)
1028-
1029-
ResultDisplayUtils.display_results_in_grid(widget.result_grid, student_labels)
1040+
cached_widgets = ResultDisplayUtils.collect_grid_widgets(widget.result_grid)
1041+
if cached_widgets and len(cached_widgets) == len(student_labels):
1042+
updated = ResultDisplayUtils.update_grid_labels(
1043+
widget.result_grid, student_labels, cached_widgets
1044+
)
1045+
if updated:
1046+
ResultDisplayUtils.dispose_widgets(student_labels)
1047+
else:
1048+
ResultDisplayUtils.display_results_in_grid(
1049+
widget.result_grid, student_labels
1050+
)
1051+
else:
1052+
ResultDisplayUtils.display_results_in_grid(widget.result_grid, student_labels)
10301053

10311054
settings = widget.manager.get_notification_settings(refresh=False)
10321055
if settings is not None:

app/common/roll_call/roll_call_manager.py

Lines changed: 125 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
from PySide6.QtCore import QObject, Signal, QTimer, QEasingCurve, QFileSystemWatcher
1+
from PySide6.QtCore import (
2+
QObject,
3+
Signal,
4+
QTimer,
5+
QEasingCurve,
6+
QFileSystemWatcher,
7+
QThreadPool,
8+
QRunnable,
9+
)
210
from PySide6.QtGui import QFont
311
from dataclasses import dataclass
412
from loguru import logger
@@ -72,6 +80,9 @@ def __init__(self):
7280
self.current_group_index = 0
7381
self.current_gender_index = 0
7482
self.half_repeat = 0
83+
self._precomputed_result = None
84+
self._precompute_key = None
85+
self._precompute_running = False
7586

7687
def build_draw_context(
7788
self,
@@ -179,6 +190,9 @@ def load_data(
179190
加载学生数据
180191
"""
181192
try:
193+
self._precomputed_result = None
194+
self._precompute_key = None
195+
self._precompute_running = False
182196
self.current_class_name = class_name
183197
self.current_group_filter = group_filter
184198
self.current_gender_filter = gender_filter
@@ -252,6 +266,9 @@ def draw_final_students(self, count):
252266
"""
253267
执行最终抽取
254268
"""
269+
precomputed = self.take_precomputed_result(count)
270+
if precomputed:
271+
return precomputed
255272
# 使用RollCallUtils中的核心抽取逻辑,它处理了去重、权重等复杂逻辑
256273
result = RollCallUtils.draw_random_students(
257274
self.current_class_name,
@@ -277,6 +294,91 @@ def save_result(self, selected_students, selected_students_dict):
277294
self.half_repeat,
278295
)
279296

297+
def _build_precompute_key(self, count):
298+
try:
299+
count = int(count or 0)
300+
except Exception:
301+
count = 0
302+
return (
303+
self.current_class_name,
304+
self.current_group_filter,
305+
self.current_gender_filter,
306+
self.current_group_index,
307+
self.current_gender_index,
308+
self.half_repeat,
309+
count,
310+
)
311+
312+
def start_precompute_final(self, count):
313+
try:
314+
count = int(count or 0)
315+
except Exception:
316+
count = 0
317+
if count <= 0 or not self.current_class_name:
318+
return
319+
key = self._build_precompute_key(count)
320+
if self._precompute_running and self._precompute_key == key:
321+
return
322+
self._precompute_key = key
323+
self._precomputed_result = None
324+
self._precompute_running = True
325+
326+
class_name = self.current_class_name
327+
group_index = self.current_group_index
328+
group_filter = self.current_group_filter
329+
gender_index = self.current_gender_index
330+
gender_filter = self.current_gender_filter
331+
half_repeat = self.half_repeat
332+
draw_count = count
333+
334+
class _Signals(QObject):
335+
loaded = Signal(object, object)
336+
337+
class _Loader(QRunnable):
338+
def __init__(self, fn, signals, key):
339+
super().__init__()
340+
self.fn = fn
341+
self.signals = signals
342+
self.key = key
343+
344+
def run(self):
345+
try:
346+
result = self.fn()
347+
self.signals.loaded.emit(self.key, result)
348+
except Exception as e:
349+
logger.exception(f"预计算最终抽取结果失败: {e}")
350+
351+
def _collect():
352+
return RollCallUtils.draw_random_students(
353+
class_name,
354+
group_index,
355+
group_filter,
356+
gender_index,
357+
gender_filter,
358+
draw_count,
359+
half_repeat,
360+
)
361+
362+
signals = _Signals()
363+
signals.loaded.connect(self._on_precompute_loaded)
364+
runnable = _Loader(_collect, signals, key)
365+
QThreadPool.globalInstance().start(runnable)
366+
367+
def _on_precompute_loaded(self, key, result):
368+
if key != self._precompute_key:
369+
return
370+
self._precomputed_result = result
371+
self._precompute_running = False
372+
373+
def take_precomputed_result(self, count):
374+
key = self._build_precompute_key(count)
375+
if self._precomputed_result and self._precompute_key == key:
376+
result = self._precomputed_result
377+
self._precomputed_result = None
378+
self._precompute_running = False
379+
return result
380+
return None
381+
280382
def reset_records(self):
281383
"""重置抽取记录"""
282384
remove_record(
@@ -321,6 +423,8 @@ def start_roll_call_draw(widget):
321423
autoplay_count = plan.autoplay_count if plan else 0
322424
animation_interval = plan.animation_interval if plan else 0
323425
animation_music = plan.animation_music if plan else None
426+
if animation in [0, 1]:
427+
manager.start_precompute_final(widget.current_count)
324428

325429
if animation == 0:
326430
if animation_music:
@@ -527,12 +631,18 @@ def play_voice_result(widget):
527631
"basic_voice_settings", "edge_tts_voice_name"
528632
)
529633

634+
class_name = (
635+
widget.final_class_name
636+
if hasattr(widget, "final_class_name") and widget.final_class_name
637+
else widget.list_combobox.currentText()
638+
)
639+
530640
widget.tts_handler.voice_play(
531641
config=voice_settings,
532642
student_names=student_names,
533643
engine_type=engine_type,
534644
voice_name=edge_tts_voice_name,
535-
class_name=widget.list_combobox.currentText(),
645+
class_name=class_name,
536646
)
537647
except Exception as e:
538648
logger.exception(f"播放语音失败: {e}", exc_info=True)
@@ -584,7 +694,19 @@ def display_result_animated(widget, selected_students, class_name):
584694
settings_group="roll_call_settings",
585695
)
586696

587-
ResultDisplayUtils.display_results_in_grid(widget.result_grid, student_labels)
697+
cached_widgets = ResultDisplayUtils.collect_grid_widgets(widget.result_grid)
698+
if cached_widgets and len(cached_widgets) == len(student_labels):
699+
updated = ResultDisplayUtils.update_grid_labels(
700+
widget.result_grid, student_labels, cached_widgets
701+
)
702+
if updated:
703+
ResultDisplayUtils.dispose_widgets(student_labels)
704+
else:
705+
ResultDisplayUtils.display_results_in_grid(
706+
widget.result_grid, student_labels
707+
)
708+
else:
709+
ResultDisplayUtils.display_results_in_grid(widget.result_grid, student_labels)
588710

589711
RollCallUtils.show_notification_if_enabled(
590712
class_name=class_name,

0 commit comments

Comments
 (0)