Skip to content

Commit 87d36fc

Browse files
committed
抽奖页面完成
1 parent 49205e4 commit 87d36fc

12 files changed

Lines changed: 579 additions & 149 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,3 +181,4 @@ cython_debug/
181181

182182
# VSCode
183183
.vscode/settings.json
184+
/.trae

app/Language/modules/custom_settings.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,21 @@
7979
"description": "控制是否显示抽奖名单切换框",
8080
"switchbutton_name": {"enable": "显示", "disable": "隐藏"},
8181
},
82+
"lottery_roll_call_list": {
83+
"name": "抽奖中的学生名单切换下拉框",
84+
"description": "控制是否显示抽奖中的学生名单切换框",
85+
"switchbutton_name": {"enable": "显示", "disable": "隐藏"},
86+
},
87+
"lottery_roll_call_range": {
88+
"name": "抽奖中的学生点名范围下拉框",
89+
"description": "控制是否显示抽奖中的学生点名范围选择框",
90+
"switchbutton_name": {"enable": "显示", "disable": "隐藏"},
91+
},
92+
"lottery_roll_call_gender": {
93+
"name": "抽奖中的学生点名性别范围下拉框",
94+
"description": "控制是否显示抽奖中的学生点名性别范围选择框",
95+
"switchbutton_name": {"enable": "显示", "disable": "隐藏"},
96+
},
8297
"lottery_quantity_label": {
8398
"name": "数量标签",
8499
"description": "控制是否显示中奖数量标签",

app/Language/modules/extraction_settings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@
298298
"name": "颜色主题设置",
299299
"description": "设置抽奖结果颜色主题",
300300
},
301-
"student_image_settings": {
301+
"lottery_image_settings": {
302302
"name": "奖品图片设置",
303303
"description": "设置抽奖结果中奖品图片显示",
304304
},

app/Language/modules/lottery_main.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
1717
"description": "停止抽奖",
1818
"pushbutton_name": "停止",
1919
},
20+
"list_combobox": {
21+
"name": "班级",
22+
"description": "选择班级",
23+
"combo_items": ["不抽取学生"],
24+
},
2025
"range_combobox": {
2126
"name": "范围",
2227
"description": "选择抽取范围",

app/page_building/another_window.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,10 @@ def setup_page():
409409
group_index,
410410
gender_index,
411411
)
412+
try:
413+
window.windowClosed.connect(lambda: getattr(page, "stop_loader", lambda: None)())
414+
except Exception:
415+
pass
412416

413417
# 使用延迟调用确保内容控件已创建
414418
QTimer.singleShot(APP_INIT_DELAY, setup_page)

app/tools/config.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1453,3 +1453,60 @@ def calculate_remaining_count(
14531453
else:
14541454
# 如果half_repeat为0,则不排除任何学生或小组
14551455
return total_count
1456+
1457+
1458+
# ======= 奖池抽取记录 =======
1459+
def record_drawn_prize(pool_name: str, prize_names):
1460+
file_path = get_resources_path("TEMP", f"draw_until_prize_{pool_name}.json")
1461+
os.makedirs(os.path.dirname(file_path), exist_ok=True)
1462+
drawn_records = _load_drawn_records(file_path)
1463+
names = _extract_student_names(prize_names)
1464+
for name in names:
1465+
if name in drawn_records:
1466+
drawn_records[name] += 1
1467+
else:
1468+
drawn_records[name] = 1
1469+
_save_drawn_records(file_path, drawn_records)
1470+
1471+
1472+
def read_drawn_record_simple(pool_name: str):
1473+
file_path = get_resources_path("TEMP", f"draw_until_prize_{pool_name}.json")
1474+
if os.path.exists(file_path):
1475+
try:
1476+
with open(file_path, "r", encoding="utf-8") as file:
1477+
data = json.load(file)
1478+
if isinstance(data, dict):
1479+
return [(name, count) for name, count in data.items()]
1480+
if isinstance(data, list):
1481+
res = []
1482+
for item in data:
1483+
if isinstance(item, str):
1484+
res.append((item, 1))
1485+
elif isinstance(item, dict) and "name" in item:
1486+
res.append((item["name"], int(item.get("count", 1))))
1487+
return res
1488+
except Exception as e:
1489+
logger.error(f"读取奖池已抽取记录失败: {e}")
1490+
return []
1491+
return []
1492+
1493+
1494+
def reset_drawn_prize_record(self, pool_name: str):
1495+
try:
1496+
pattern = os.path.join("app", "resources", "TEMP", f"draw_*_prize_{pool_name}.json")
1497+
for fp in glob.glob(pattern):
1498+
try:
1499+
os.remove(fp)
1500+
except OSError as e:
1501+
logger.error(f"删除文件{fp}失败: {e}")
1502+
show_notification(
1503+
NotificationType.INFO,
1504+
NotificationConfig(
1505+
title="提示",
1506+
content=f"已重置{pool_name}奖池抽取记录",
1507+
icon=FluentIcon.INFO,
1508+
),
1509+
parent=self,
1510+
)
1511+
except Exception as e:
1512+
logger.error(f"重置奖池抽取记录失败: {e}")

app/tools/history.py

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ def save_history_data(history_type: str, file_name: str, data: Dict[str, Any]) -
9292
return True
9393
except Exception as e:
9494
logger.error(f"保存历史记录数据失败: {e}")
95-
return False
95+
return False
9696

9797

9898
def get_all_history_names(history_type: str) -> List[str]:
@@ -200,6 +200,63 @@ def get_individual_statistics(
200200
return 0
201201
return len(student_history)
202202

203+
# ==================================================
204+
# 保存抽奖历史函数
205+
# ==================================================
206+
def save_lottery_history(pool_name: str, selected_students: List[Dict[str, Any]], group_filter: str, gender_filter: str) -> bool:
207+
"""保存抽奖历史(基于奖池名称)
208+
209+
Args:
210+
pool_name: 奖池名称
211+
selected_students: 学生字典列表(包含 name、id、exist 等)
212+
group_filter: 抽取时的小组过滤器
213+
gender_filter: 抽取时的性别过滤器
214+
215+
Returns:
216+
bool: 保存是否成功
217+
"""
218+
try:
219+
history_data = load_history_data("lottery", pool_name)
220+
lotterys = history_data.get("lotterys", {})
221+
group_stats = history_data.get("group_stats", {})
222+
gender_stats = history_data.get("gender_stats", {})
223+
total_stats = history_data.get("total_stats", 0)
224+
225+
now_str = datetime.now().isoformat(timespec="seconds")
226+
227+
for student in selected_students or []:
228+
name = student.get("name", "")
229+
if not name:
230+
continue
231+
entry = lotterys.get(name)
232+
if not isinstance(entry, dict):
233+
entry = {"total_count": 0, "rounds_missed": 0, "last_drawn_time": "", "history": []}
234+
entry["total_count"] = int(entry.get("total_count", 0)) + 1
235+
entry["last_drawn_time"] = now_str
236+
hist = entry.get("history", [])
237+
if not isinstance(hist, list):
238+
hist = []
239+
hist.append({"draw_time": now_str, "draw_group": group_filter, "draw_gender": gender_filter})
240+
entry["history"] = hist
241+
lotterys[name] = entry
242+
243+
# 更新统计
244+
if group_filter:
245+
group_stats[group_filter] = int(group_stats.get(group_filter, 0)) + len(selected_students or [])
246+
if gender_filter:
247+
gender_stats[gender_filter] = int(gender_stats.get(gender_filter, 0)) + len(selected_students or [])
248+
total_stats = int(total_stats) + len(selected_students or [])
249+
250+
history_data["lotterys"] = lotterys
251+
history_data["group_stats"] = group_stats
252+
history_data["gender_stats"] = gender_stats
253+
history_data["total_stats"] = total_stats
254+
255+
return save_history_data("lottery", pool_name, history_data)
256+
except Exception as e:
257+
logger.error(f"保存抽奖历史失败: {e}")
258+
return False
259+
203260

204261
# ==================================================
205262
# 权重格式化函数

app/tools/lottery_utils.py

Lines changed: 119 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44
import json
55
from random import SystemRandom
66

7-
from app.tools.list import get_group_list, get_student_list, filter_students_data
8-
from app.tools.history import calculate_weight
7+
from app.tools.list import get_group_list, get_student_list, filter_students_data, get_pool_list
8+
from app.tools.history import calculate_weight, load_history_data
99
from app.tools.config import (
1010
calculate_remaining_count,
1111
read_drawn_record,
12+
read_drawn_record_simple,
1213
reset_drawn_record,
1314
)
1415
from app.tools.path_utils import get_resources_path, open_file
@@ -68,7 +69,7 @@ def update_many_count_label_text(
6869
tuple: (总人数, 剩余人数, 格式化文本)
6970
"""
7071
# 根据范围计算实际人数
71-
total_count = RollCallUtils.get_total_count(
72+
total_count = LotteryUtils.get_total_count(
7273
list_combobox_text, range_combobox_index, range_combobox_text
7374
)
7475

@@ -243,6 +244,121 @@ def draw_random_students(
243244
"gender_filter": gender_filter,
244245
}
245246

247+
@staticmethod
248+
def get_prize_total_count(pool_name: str) -> int:
249+
"""获取奖池奖品总数"""
250+
try:
251+
from app.tools.list import get_pool_list
252+
return len(get_pool_list(pool_name))
253+
except Exception:
254+
return 0
255+
256+
@staticmethod
257+
def update_prize_many_count_label_text(pool_name: str):
258+
"""生成奖品总数/剩余显示文本"""
259+
total_count = LotteryUtils.get_prize_total_count(pool_name)
260+
remaining_count = LotteryUtils.calculate_prize_remaining_count(pool_name)
261+
if remaining_count == 0:
262+
remaining_count = total_count
263+
text_template = get_any_position_value("lottery", "many_count_label", "text_0")
264+
formatted_text = text_template.format(total_count=total_count, remaining_count=remaining_count)
265+
return total_count, remaining_count, formatted_text
266+
267+
@staticmethod
268+
def draw_random_prizes(pool_name: str, current_count: int):
269+
"""按权重抽取奖品"""
270+
try:
271+
from app.tools.config import read_drawn_record
272+
items = get_pool_list(pool_name)
273+
if not items:
274+
return {"selected_prizes": [], "pool_name": pool_name, "selected_prizes_dict": []}
275+
# 非重复/半重复处理:根据 TEMP 记录过滤已达阈值的奖品(与 roll_call 一致)
276+
threshold = LotteryUtils._get_prize_draw_threshold()
277+
if threshold is not None:
278+
drawn_records = read_drawn_record_simple(pool_name)
279+
drawn_counts = {name: cnt for name, cnt in drawn_records}
280+
available = []
281+
for i in items:
282+
name = i.get("name", "")
283+
cnt = int(drawn_counts.get(name, 0))
284+
if cnt < threshold:
285+
available.append(i)
286+
items = available
287+
if not items:
288+
return {"reset_required": True}
289+
# 准备权重
290+
weights = [float(i.get("weight", 1)) for i in items]
291+
draw = min(current_count, len(items))
292+
selected = []
293+
selected_dict = []
294+
for _ in range(draw):
295+
if not items:
296+
break
297+
total_weight = sum(weights)
298+
if total_weight <= 0:
299+
idx = system_random.randint(0, len(items) - 1)
300+
else:
301+
rv = system_random.uniform(0, total_weight)
302+
cum = 0
303+
idx = 0
304+
for i, w in enumerate(weights):
305+
cum += w
306+
if rv <= cum:
307+
idx = i
308+
break
309+
chosen = items[idx]
310+
selected.append((chosen.get("id"), chosen.get("name"), chosen.get("exist", True)))
311+
selected_dict.append(chosen)
312+
items.pop(idx)
313+
weights.pop(idx)
314+
return {
315+
"selected_prizes": selected,
316+
"pool_name": pool_name,
317+
"selected_prizes_dict": selected_dict,
318+
}
319+
except Exception:
320+
return {"selected_prizes": [], "pool_name": pool_name, "selected_prizes_dict": []}
321+
322+
@staticmethod
323+
def _get_prize_draw_threshold():
324+
"""获取奖品抽取阈值:None 表示可重复;1 表示不重复;半重复返回次数阈值"""
325+
try:
326+
mode = readme_settings_async("lottery_settings", "draw_mode")
327+
if mode == 1:
328+
return 1
329+
elif mode == 2:
330+
hr = readme_settings_async("lottery_settings", "half_repeat")
331+
try:
332+
return int(hr) if hr else 1
333+
except Exception:
334+
return 1
335+
else:
336+
return None
337+
except Exception:
338+
return None
339+
340+
@staticmethod
341+
def calculate_prize_remaining_count(pool_name: str) -> int:
342+
"""计算剩余可抽奖品数量,考虑不重复/半重复设置"""
343+
try:
344+
from app.tools.list import get_pool_list
345+
from app.tools.config import read_drawn_record
346+
threshold = LotteryUtils._get_prize_draw_threshold()
347+
total = len(get_pool_list(pool_name))
348+
if threshold is None:
349+
return total
350+
drawn_records = read_drawn_record_simple(pool_name)
351+
drawn_counts = {name: cnt for name, cnt in drawn_records}
352+
remain = 0
353+
for i in get_pool_list(pool_name):
354+
name = i.get("name", "")
355+
cnt = int(drawn_counts.get(name, 0))
356+
if cnt < threshold:
357+
remain += 1
358+
return remain
359+
except Exception:
360+
return 0
361+
246362
@staticmethod
247363
def draw_random_groups(students_dict_list, current_count, draw_type):
248364
"""

app/tools/settings_default_storage.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,9 @@
363363
"lottery_quantity_control": {"default_value": True},
364364
"lottery_start_button": {"default_value": True},
365365
"lottery_list": {"default_value": True},
366+
"lottery_roll_call_list": {"default_value": True},
367+
"lottery_roll_call_range": {"default_value": True},
368+
"lottery_roll_call_gender": {"default_value": True},
366369
"lottery_remaining_button": {"default_value": True},
367370
"lottery_quantity_label": {"default_value": True},
368371
"custom_method": {"default_value": 1},

0 commit comments

Comments
 (0)