Python 登记积分脚本
前置知识 openpyxl:
openpyxl 是一个强大的 Python 库,用于处理 Excel 文件,允许读取、编辑和创建 Excel 工作簿和工作表。无论是需要自动化处理大量数据,还是创建漂亮的报告,openpyxl 都是一个强大的工具。
安装方式:
python -m pip install openpyxl
前置知识 pathlib
pathlib 是一个对文件路径、操作进行支持的库。
前置知识 tkinter
本文的代码中使用 ttk 的 ProgressBar
。
filedialog
是一个选择文件的类,用于弹出窗口,让用户选择;
simpledialog
是用来弹出窗口,让用户输入数据的;
messagebox
是用来弹出选择(确定、取消等)窗口。
代码实现
import os
import time
import json
from pathlib import Path
from openpyxl import load_workbook
from tkinter import *
from tkinter import messagebox, simpledialog, filedialog, ttk
# 默认配置文件路径
CONFIG_FILE = "settings.json"
# 加载配置
def load_config():
try:
with open(CONFIG_FILE, "r") as f:
return json.load(f)
except FileNotFoundError:
return {
"settings": {
"sr": 2,
"sc": 2,
"mw": 4,
"file_path": "",
"wn": "积分2024.9",
},
"exclude": [],
}
# 保存配置
def save_config(config):
with open(CONFIG_FILE, "w") as f:
json.dump(config, f, indent=4)
# 主界面
class MainApp:
def __init__(self):
self.config = load_config()
self.root = Tk()
self.root.geometry("400x200")
self.root.title("积分登记系统")
Label(self.root, text="积分登记系统", font=("Arial", 16)).pack(pady=20)
Button(self.root, text="登记积分", command=self.open_join).pack(pady=5)
Button(self.root, text="设置", command=self.open_settings).pack(pady=5)
Button(self.root, text="计算月份总分", command=self.open_sum_month).pack(pady=5)
Button(self.root, text="帮助", command=self.show_help).pack(pady=5)
self.root.protocol("WM_DELETE_WINDOW", self.exit_program)
self.root.mainloop()
def open_join(self):
if not Path(self.config["settings"]["file_path"]).is_file():
messagebox.showerror("错误", "未找到表格文件,请先在设置中配置表格路径!")
return
week = simpledialog.askinteger("登记积分", "请输入周数:")
if week is not None:
JoinWindow(self.config, week)
def open_settings(self):
SettingsWindow(self.config)
def open_sum_month(self):
month = simpledialog.askinteger("计算月份总分", "请输入月份:")
if month is not None:
SumMonthWindow(self.config, month)
def show_help(self):
messagebox.showinfo("帮助", "输入格式为:号数 积分\n例如:1 2\n按回车键发送,按Delete键删除")
def exit_program(self):
if messagebox.askokcancel("退出", "确认退出程序吗?"):
self.root.destroy()
os._exit(0)
# 设置窗口
class SettingsWindow:
def __init__(self, config):
self.config = config
self.root = Tk()
self.root.geometry("400x300")
self.root.title("设置")
Label(self.root, text="起始行:").grid(row=0, column=0, padx=10, pady=5)
self.sr = Entry(self.root)
self.sr.grid(row=0, column=1, pady=5)
self.sr.insert(0, str(config["settings"]["sr"]))
Label(self.root, text="起始列:").grid(row=1, column=0, padx=10, pady=5)
self.sc = Entry(self.root)
self.sc.grid(row=1, column=1, pady=5)
self.sc.insert(0, str(config["settings"]["sc"]))
Label(self.root, text="月份求和周数:").grid(row=2, column=0, padx=10, pady=5)
self.mw = Entry(self.root)
self.mw.grid(row=2, column=1, pady=5)
self.mw.insert(0, str(config["settings"]["mw"]))
Label(self.root, text="表格文件:").grid(row=3, column=0, padx=10, pady=5)
Button(self.root, text="选择文件", command=self.choose_file).grid(row=3, column=1)
self.file_label = Label(self.root, text=os.path.basename(config["settings"]["file_path"]))
self.file_label.grid(row=3, column=2)
Label(self.root, text="工作簿名称:").grid(row=4, column=0, padx=10, pady=5)
self.wn = Entry(self.root)
self.wn.grid(row=4, column=1, pady=5)
self.wn.insert(0, config["settings"]["wn"])
Label(self.root, text="排除号数 (用空格分隔):").grid(row=5, column=0, padx=10, pady=5)
self.exclude = Entry(self.root)
self.exclude.grid(row=5, column=1, pady=5)
self.exclude.insert(0, " ".join(map(str, config["exclude"])))
Button(self.root, text="保存", command=self.save_settings).grid(row=6, column=1, pady=20)
def choose_file(self):
file_path = filedialog.askopenfilename(filetypes=[("Excel files", "*.xlsx;*.xlsm")])
if file_path:
self.config["settings"]["file_path"] = file_path
self.file_label.config(text=os.path.basename(file_path))
def save_settings(self):
try:
self.config["settings"]["sr"] = int(self.sr.get())
self.config["settings"]["sc"] = int(self.sc.get())
self.config["settings"]["mw"] = int(self.mw.get())
self.config["settings"]["wn"] = self.wn.get()
self.config["exclude"] = list(map(int, self.exclude.get().split()))
save_config(self.config)
messagebox.showinfo("成功", "设置已保存!")
self.root.destroy()
except ValueError:
messagebox.showerror("错误", "输入格式错误,请检查!")
# 计算月份总分窗口
class SumMonthWindow:
def __init__(self, config, month):
self.config = config
self.month = month
self.root = Tk()
self.root.geometry("400x150")
self.root.title("计算月份总分")
Label(self.root, text="正在计算月份总分...", font=("Arial", 14)).pack(pady=10)
self.progress = ttk.Progressbar(self.root, mode="determinate")
self.progress.pack(pady=10)
Button(self.root, text="开始", command=self.start_calculation).pack(pady=5)
def start_calculation(self):
try:
wb = load_workbook(self.config["settings"]["file_path"])
ws = wb[self.config["settings"]["wn"]]
sr, sc, mw = self.config["settings"]["sr"], self.config["settings"]["sc"], self.config["settings"]["mw"]
week_col = sc + self.month * mw
ws.insert_cols(week_col + 1)
for i in range(54):
self.progress["value"] = (i + 1) / 54 * 100
ws.cell(row=sr + i, column=week_col + 1).value = f"=SUM({ws.cell(row=sr + i, column=week_col - mw + 1).coordinate}:{ws.cell(row=sr + i, column=week_col).coordinate})"
self.root.update_idletasks()
time.sleep(0.05)
wb.save(self.config["settings"]["file_path"])
wb.close()
messagebox.showinfo("成功", "月份总分计算完成!")
except Exception as e:
messagebox.showerror("错误", f"计算失败:{e}")
finally:
self.root.destroy()
# 登记积分窗口
class JoinWindow:
def __init__(self, config, week):
self.config = config
self.week = week
self.file_path = config["settings"]["file_path"]
self.sheet_name = config["settings"]["wn"]
self.exclude = config["exclude"]
self.start_row = config["settings"]["sr"]
self.start_col = config["settings"]["sc"] + week - 1
self.same_score_count = 0
# 加载 Excel 工作簿和工作表
self.wb = load_workbook(self.file_path)
self.ws = self.wb[self.sheet_name]
self.root = Tk()
self.root.geometry("400x400")
self.root.title("登记积分")
Label(self.root, text=f"当前周:{week}", font=("Arial", 14)).pack(pady=10)
self.entry = Entry(self.root)
self.entry.pack(pady=10)
self.listbox = Listbox(self.root)
self.listbox.pack(pady=10)
Button(self.root, text="添加", command=self.add_score).pack(side=LEFT, padx=10)
Button(self.root, text="删除", command=self.delete_score).pack(side=RIGHT, padx=10)
self.root.protocol("WM_DELETE_WINDOW", self.save_and_exit)
self.root.mainloop()
def add_score(self):
try:
input_text = self.entry.get().strip()
if not input_text:
raise ValueError("输入不能为空!")
# 检查输入格式
parts = input_text.split()
if len(parts) != 2 or not parts[0].isdigit() or not parts[1].isdigit():
raise ValueError("输入格式错误,请输入:号数 积分\n例如:1 2")
number, score = map(int, parts)
if number < 1 or number > 55:
raise ValueError("号数必须在 1 到 55 之间!")
if number in self.exclude:
if not messagebox.askyesno("提示", f"号数 {number} 在排除列表中,是否继续登记?"):
return
# 检查是否覆盖已有值
cell = self.ws.cell(row=self.start_row + number - 1, column=self.start_col)
if cell.value is not None:
if not messagebox.askyesno("提示", f"号数 {number} 已有积分,是否覆盖?"):
return
# 添加积分
cell.value = score
self.listbox.insert(END, f"{number} {score}")
self.entry.delete(0, END)
# 检查当前周与前几周的积分是否有超过7个相同
for week_offset in range(self.config["settings"]["sc"], self.start_col):
if self.ws.cell(row=self.start_row + number - 1, column=week_offset).value == score:
same_score_count += 1
if same_score_count > 7:
messagebox.showinfo("提示", f"当前周与前几周有超过7个积分为 {score},请检查!")
except ValueError as e:
messagebox.showerror("输入错误", str(e))
except Exception as e:
messagebox.showerror("错误", f"无法添加积分:{e}")
def delete_score(self):
try:
selected = self.listbox.curselection()
if not selected:
raise ValueError("请先选择要删除的记录!")
# 从列表框中删除选中记录
record = self.listbox.get(selected[0])
number, _ = map(int, record.split())
self.ws.cell(row=self.start_row + number - 1, column=self.start_col).value = None
self.listbox.delete(selected[0])
except ValueError as e:
messagebox.showerror("错误", str(e))
except Exception as e:
messagebox.showerror("错误", f"无法删除积分:{e}")
def save_and_exit(self):
if messagebox.askyesno("提示", "是否保存更改?"):
try:
self.wb.save(self.file_path)
except Exception as e:
messagebox.showerror("错误", f"保存文件失败:{e}")
self.wb.close()
self.root.destroy()
if __name__ == "__main__":
MainApp()