镜像自地址
https://github.com/a13zhy/a13rollcall.git
已同步 2026-10-20 03:17:47 +08:00
321 行
12 KiB
Python
321 行
12 KiB
Python
import os
|
|
from datetime import datetime
|
|
import copy
|
|
|
|
|
|
class DataManager:
|
|
def __init__(self, config_manager):
|
|
self.config_mgr = config_manager
|
|
self.stats = {}
|
|
self.text_progress = {}
|
|
|
|
def _parse_student_line(self, line):
|
|
line = line.strip()
|
|
if not line:
|
|
return None
|
|
parts = line.split(maxsplit=1)
|
|
if len(parts) == 2 and parts[0].isdigit():
|
|
name = parts[1].strip()
|
|
else:
|
|
name = line
|
|
if name.isdigit() or not name:
|
|
return None
|
|
return name
|
|
|
|
def load_students(self):
|
|
file_path = self.config_mgr.get("files", "students")
|
|
students = []
|
|
error = ""
|
|
if not os.path.exists(file_path):
|
|
error = f"学生名单文件 {file_path} 不存在,可在设置中创建"
|
|
return students, error
|
|
try:
|
|
with open(file_path, "r", encoding="utf-8") as f:
|
|
for line in f:
|
|
name = self._parse_student_line(line)
|
|
if name:
|
|
students.append(name)
|
|
if not students:
|
|
error = "学生名单为空,请检查文件内容"
|
|
except Exception as e:
|
|
error = f"读取学生名单失败:{str(e)}"
|
|
return students, error
|
|
|
|
def load_texts(self):
|
|
file_path = self.config_mgr.get("files", "texts")
|
|
texts = []
|
|
error = ""
|
|
if not os.path.exists(file_path):
|
|
error = f"课文库文件 {file_path} 不存在,可在设置中创建"
|
|
return texts, error
|
|
try:
|
|
current_title = None
|
|
current_paragraphs = []
|
|
current_para = ""
|
|
with open(file_path, "r", encoding="utf-8") as f:
|
|
for line in f:
|
|
line = line.rstrip("\n")
|
|
if line.startswith("【") and line.endswith("】"):
|
|
if current_title is not None:
|
|
if current_para.strip():
|
|
current_paragraphs.append(current_para.strip())
|
|
texts.append({
|
|
"title": current_title,
|
|
"paragraphs": current_paragraphs
|
|
})
|
|
current_title = line[1:-1]
|
|
current_paragraphs = []
|
|
current_para = ""
|
|
elif line.strip() == "":
|
|
if current_para.strip():
|
|
current_paragraphs.append(current_para.strip())
|
|
current_para = ""
|
|
else:
|
|
if current_para:
|
|
current_para += "\n" + line
|
|
else:
|
|
current_para = line
|
|
if current_title is not None:
|
|
if current_para.strip():
|
|
current_paragraphs.append(current_para.strip())
|
|
texts.append({
|
|
"title": current_title,
|
|
"paragraphs": current_paragraphs
|
|
})
|
|
except Exception as e:
|
|
error = f"读取课文库失败:{str(e)}"
|
|
return texts, error
|
|
|
|
def save_record(self, student_name, text_title, paragraph_info, status, remark="课堂抽查"):
|
|
file_path = self.config_mgr.get("files", "records")
|
|
time_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
line = f"{time_str} {student_name} {text_title} {paragraph_info} {status} {remark}\n"
|
|
try:
|
|
with open(file_path, "a", encoding="utf-8") as f:
|
|
f.write(line)
|
|
except Exception:
|
|
pass
|
|
|
|
def get_student_status(self, name):
|
|
return self.config_mgr.get("student_status", name) or "未背过"
|
|
|
|
def set_student_status(self, name, status):
|
|
self.config_mgr.set(status, "student_status", name)
|
|
self.config_mgr.trigger_hooks("status_change", name, status)
|
|
self.config_mgr.save_config()
|
|
self.update_status_stats(name, status)
|
|
|
|
def get_student_weight(self, name):
|
|
w = self.config_mgr.get("student_weights", name)
|
|
return w if w is not None else 1
|
|
|
|
def set_student_weight(self, name, weight, skip_save=False):
|
|
self.config_mgr.set(weight, "student_weights", name)
|
|
if not skip_save:
|
|
self.config_mgr.save_config()
|
|
|
|
def reset_all_weights(self, students):
|
|
for name in students:
|
|
self.set_student_weight(name, 1, skip_save=True)
|
|
self.config_mgr.save_config()
|
|
|
|
def load_stats(self):
|
|
file_path = self.config_mgr.get("files", "stats")
|
|
self.stats = {}
|
|
if not os.path.exists(file_path):
|
|
return
|
|
try:
|
|
with open(file_path, "r", encoding="utf-8") as f:
|
|
for line in f:
|
|
line = line.strip()
|
|
if not line:
|
|
continue
|
|
parts = line.split()
|
|
if len(parts) < 6:
|
|
continue
|
|
try:
|
|
name = parts[0]
|
|
draw_count = int(parts[1])
|
|
mastered = int(parts[2])
|
|
familiar = int(parts[3])
|
|
unlearned = int(parts[4])
|
|
score = int(parts[5])
|
|
self.stats[name] = {
|
|
"draw_count": draw_count,
|
|
"mastered": mastered,
|
|
"familiar": familiar,
|
|
"unlearned": unlearned,
|
|
"score": score
|
|
}
|
|
except (ValueError, IndexError):
|
|
continue
|
|
except Exception:
|
|
self.stats = {}
|
|
|
|
def save_stats(self):
|
|
file_path = self.config_mgr.get("files", "stats")
|
|
try:
|
|
with open(file_path, "w", encoding="utf-8") as f:
|
|
for name, data in self.stats.items():
|
|
line = f"{name} {data['draw_count']} {data['mastered']} {data['familiar']} {data['unlearned']} {data['score']}\n"
|
|
f.write(line)
|
|
except Exception:
|
|
pass
|
|
|
|
def _init_stats_for_new_students(self, students):
|
|
for name in students:
|
|
if name not in self.stats:
|
|
self.stats[name] = {
|
|
"draw_count": 0,
|
|
"mastered": 0,
|
|
"familiar": 0,
|
|
"unlearned": 0,
|
|
"score": 100
|
|
}
|
|
|
|
def increment_draw_count(self, name):
|
|
if name not in self.stats:
|
|
self._init_stats_for_new_students([name])
|
|
self.stats[name]["draw_count"] += 1
|
|
self.save_stats()
|
|
|
|
def update_status_stats(self, name, status):
|
|
if name not in self.stats:
|
|
self._init_stats_for_new_students([name])
|
|
data = self.stats[name]
|
|
if status == "已背过":
|
|
data["mastered"] += 1
|
|
data["score"] += 10
|
|
elif status == "未背熟":
|
|
data["familiar"] += 1
|
|
data["score"] -= 5
|
|
elif status == "未背过":
|
|
data["unlearned"] += 1
|
|
data["score"] -= 15
|
|
self.save_stats()
|
|
|
|
|
|
def revert_status_stats(self, name, status):
|
|
if name not in self.stats:
|
|
return
|
|
data = self.stats[name]
|
|
if status == "已背过":
|
|
data["mastered"] = max(0, data["mastered"] - 1)
|
|
data["score"] -= 10
|
|
elif status == "未背熟":
|
|
data["familiar"] = max(0, data["familiar"] - 1)
|
|
data["score"] += 5
|
|
elif status == "未背过":
|
|
data["unlearned"] = max(0, data["unlearned"] - 1)
|
|
data["score"] += 15
|
|
self.save_stats()
|
|
|
|
def get_all_stats(self):
|
|
return copy.deepcopy(self.stats)
|
|
|
|
def get_ranking(self):
|
|
ranking = [(name, data["score"]) for name, data in self.stats.items()]
|
|
ranking.sort(key=lambda x: x[1], reverse=True)
|
|
return ranking
|
|
|
|
def get_text_progress(self, text_title):
|
|
return self.config_mgr.get("text_progress", text_title) or 0
|
|
|
|
def set_text_progress(self, text_title, index):
|
|
self.config_mgr.set(index, "text_progress", text_title)
|
|
self.config_mgr.save_config()
|
|
def _get_rounds_file(self):
|
|
return os.path.join(os.path.dirname(self.config_mgr.get("files", "stats") or "stats.txt"), "rounds_history.json")
|
|
|
|
def save_round(self, round_data):
|
|
import json
|
|
file_path = self._get_rounds_file()
|
|
rounds = self._load_rounds()
|
|
rounds.append(round_data)
|
|
try:
|
|
with open(file_path, "w", encoding="utf-8") as f:
|
|
json.dump(rounds, f, ensure_ascii=False, indent=2)
|
|
except Exception as e:
|
|
print(f"保存轮次记录失败: {e}")
|
|
|
|
def _load_rounds(self):
|
|
import json
|
|
file_path = self._get_rounds_file()
|
|
if not os.path.exists(file_path):
|
|
return []
|
|
try:
|
|
with open(file_path, "r", encoding="utf-8") as f:
|
|
return json.load(f)
|
|
except Exception:
|
|
return []
|
|
|
|
def get_all_rounds(self):
|
|
rounds = self._load_rounds()
|
|
result = []
|
|
for r in rounds:
|
|
result.append({
|
|
"round_id": r.get("round_id", 0),
|
|
"start_time": r.get("start_time", ""),
|
|
"end_time": r.get("end_time", ""),
|
|
"total": r.get("summary", {}).get("total", 0),
|
|
"mastered": r.get("summary", {}).get("mastered", 0),
|
|
"familiar": r.get("summary", {}).get("familiar", 0),
|
|
"unlearned": r.get("summary", {}).get("unlearned", 0),
|
|
})
|
|
return result
|
|
|
|
def get_round_detail(self, round_id):
|
|
rounds = self._load_rounds()
|
|
for r in rounds:
|
|
if r.get("round_id") == round_id:
|
|
return r
|
|
return None
|
|
|
|
def delete_round(self, round_id):
|
|
file_path = self._get_rounds_file()
|
|
rounds = self._load_rounds()
|
|
rounds = [r for r in rounds if r.get("round_id") != round_id]
|
|
try:
|
|
with open(file_path, "w", encoding="utf-8") as f:
|
|
json.dump(rounds, f, ensure_ascii=False, indent=2)
|
|
return True
|
|
except Exception:
|
|
return False
|
|
|
|
def get_text_stats(self):
|
|
rounds = self._load_rounds()
|
|
text_stats = {}
|
|
for r in rounds:
|
|
for rec in r.get("records", []):
|
|
text = rec.get("text", "未抽取课文")
|
|
if text not in text_stats:
|
|
text_stats[text] = {"total": 0, "mastered": 0, "familiar": 0, "unlearned": 0, "students": []}
|
|
text_stats[text]["total"] += 1
|
|
status = rec.get("status", "")
|
|
if status == "已背过":
|
|
text_stats[text]["mastered"] += 1
|
|
elif status == "未背熟":
|
|
text_stats[text]["familiar"] += 1
|
|
elif status == "未背过":
|
|
text_stats[text]["unlearned"] += 1
|
|
text_stats[text]["students"].append(rec.get("student", ""))
|
|
return text_stats
|
|
|
|
def get_draw_records(self):
|
|
rounds = self._load_rounds()
|
|
all_records = []
|
|
for r in rounds:
|
|
for rec in r.get("records", []):
|
|
rec_copy = rec.copy()
|
|
rec_copy["round_id"] = r.get("round_id", 0)
|
|
rec_copy["round_start"] = r.get("start_time", "")
|
|
all_records.append(rec_copy)
|
|
all_records.sort(key=lambda x: x.get("time", ""), reverse=True)
|
|
return all_records
|
|
|
|
def next_round_id(self):
|
|
rounds = self._load_rounds()
|
|
if not rounds:
|
|
return 1
|
|
return max(r.get("round_id", 0) for r in rounds) + 1
|