1
This commit is contained in:
4
components/__init__.py
Normal file
4
components/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from .constants import *
|
||||
from .page0 import Page0
|
||||
from .page1 import Page1
|
||||
from .page2 import Page2
|
||||
26
components/constants.py
Normal file
26
components/constants.py
Normal file
@@ -0,0 +1,26 @@
|
||||
import tkinter.font as font
|
||||
|
||||
# 定义
|
||||
BG_COLOR = "#f4f6f9" # 背景色
|
||||
FG_COLOR = "#2c3e50" # 主文字色
|
||||
ACCENT_COLOR = "#3498db" # 主按钮色
|
||||
CORRECT_COLOR = "#d4edda" # 正确答案 淡绿色
|
||||
MISS_COLOR = "#5bc04b" # 漏选 绿色
|
||||
WRONG_COLOR = "#f8d7da" # 错误 红色
|
||||
SELECTED_COLOR = "#d6eaf8" # 选中/悬浮 浅蓝色
|
||||
FONT_FAMILY = "微软雅黑"
|
||||
|
||||
# 映射
|
||||
indexMap = {6: "A. ", 7: "B. ", 8: "C. ", 9: "D. "}
|
||||
|
||||
# 全局
|
||||
bottom_options = ["全部的题", "未做过的题", "错过的题", ]
|
||||
instructions = f"""后面的选项仅在'{bottom_options[0]}'未被勾选时起效"""
|
||||
|
||||
# 样式
|
||||
# 创建字体
|
||||
class Fonts:
|
||||
def __init__(self):
|
||||
self.title = font.Font(family=FONT_FAMILY, size=18, weight="bold")
|
||||
self.button = font.Font(family=FONT_FAMILY, size=14)
|
||||
self.text = font.Font(family=FONT_FAMILY, size=13)
|
||||
307
components/page0.py
Normal file
307
components/page0.py
Normal file
@@ -0,0 +1,307 @@
|
||||
# 答题页
|
||||
|
||||
import tkinter as tk
|
||||
import random
|
||||
from functools import partial
|
||||
from components import *
|
||||
|
||||
# 选项
|
||||
class WrappingButton(tk.Frame):
|
||||
def __init__(self, parent, text="", command=None,title_font=None,button_font=None,text_font=None, **kwargs):
|
||||
tk.Frame.__init__(self, parent, **kwargs)
|
||||
self.configure(bg=BG_COLOR, relief="raised", bd=1)
|
||||
|
||||
self.title_front = title_font
|
||||
self.button_font = button_font
|
||||
self.text_font = text_font
|
||||
|
||||
self.label = tk.Label(
|
||||
self,
|
||||
text=text,
|
||||
justify="left",
|
||||
anchor="w",
|
||||
wraplength=800,
|
||||
padx=15,
|
||||
pady=12,
|
||||
bg=BG_COLOR,
|
||||
fg=FG_COLOR,
|
||||
font=self.button_font
|
||||
)
|
||||
self.label.pack(fill="both", expand=True)
|
||||
|
||||
# 绑定点击事件
|
||||
self.bind("<Button-1>", lambda e: command() if command else None)
|
||||
self.label.bind("<Button-1>", lambda e: command() if command else None)
|
||||
|
||||
# 绑定鼠标悬停事件
|
||||
self.bind("<Enter>", self._on_enter)
|
||||
self.bind("<Leave>", self._on_leave)
|
||||
self.label.bind("<Enter>", self._on_enter)
|
||||
self.label.bind("<Leave>", self._on_leave)
|
||||
|
||||
# 当前状态
|
||||
self.current_style = "Default"
|
||||
self._state = "normal"
|
||||
|
||||
def _on_enter(self, event):
|
||||
if self._state == "disabled":
|
||||
return
|
||||
if self.current_style == "Default":
|
||||
self.label.configure(bg=SELECTED_COLOR)
|
||||
self.configure(bg=SELECTED_COLOR)
|
||||
|
||||
def _on_leave(self, event):
|
||||
if self._state == "disabled":
|
||||
return
|
||||
if self.current_style == "Default":
|
||||
self.label.configure(bg=BG_COLOR)
|
||||
self.configure(bg=BG_COLOR)
|
||||
|
||||
def config(self, **kwargs):
|
||||
if "text" in kwargs:
|
||||
self.label.config(text=kwargs["text"])
|
||||
kwargs.pop("text")
|
||||
|
||||
if "style" in kwargs:
|
||||
style = kwargs["style"].split(".")[0]
|
||||
self.current_style = style
|
||||
|
||||
if style == "Default":
|
||||
self.label.configure(bg=BG_COLOR, fg=FG_COLOR)
|
||||
self.configure(bg=BG_COLOR)
|
||||
elif style == "Selected":
|
||||
self.label.configure(bg=SELECTED_COLOR, fg=FG_COLOR)
|
||||
self.configure(bg=SELECTED_COLOR)
|
||||
elif style == "Correct":
|
||||
self.label.configure(bg=CORRECT_COLOR, fg="#155724")
|
||||
self.configure(bg=CORRECT_COLOR)
|
||||
elif style == "Miss":
|
||||
self.label.configure(bg=MISS_COLOR, fg="#155724")
|
||||
self.configure(bg=MISS_COLOR)
|
||||
elif style == "Wrong":
|
||||
self.label.configure(bg=WRONG_COLOR, fg="#721c24")
|
||||
self.configure(bg=WRONG_COLOR)
|
||||
|
||||
kwargs.pop("style")
|
||||
|
||||
if "state" in kwargs:
|
||||
self._state = kwargs["state"]
|
||||
if kwargs["state"] == "disabled":
|
||||
self.label.configure(fg="#999999")
|
||||
else:
|
||||
self.label.configure(fg=FG_COLOR)
|
||||
kwargs.pop("state")
|
||||
|
||||
tk.Frame.config(self, **kwargs)
|
||||
|
||||
class Page0(tk.Frame):
|
||||
# 定义
|
||||
indexMap = {6: "A. ", 7: "B. ", 8: "C. ", 9: "D. "} # 映射
|
||||
|
||||
def __init__(self, parent,fonts,c_backend, **kwargs):
|
||||
tk.Frame.__init__(self, parent, **kwargs)
|
||||
# 全局变量
|
||||
self.buttons = []
|
||||
self.text1 = None
|
||||
self.text2 = None
|
||||
self.row = []
|
||||
self.correct_answer = []
|
||||
self.my_answer = [0, 0, 0, 0]
|
||||
self.shuffle_index = [6, 7, 8, 9]
|
||||
self.flag = False
|
||||
self.button_disable = True
|
||||
|
||||
# 入参
|
||||
self.c_backend = c_backend
|
||||
|
||||
# 题目
|
||||
frame_top = tk.Frame(parent, bg=BG_COLOR)
|
||||
frame_top.pack(pady=15, padx=20, fill='x')
|
||||
|
||||
self.text1 = tk.Text(
|
||||
frame_top,
|
||||
font=fonts.title,
|
||||
wrap='word',
|
||||
height=5,
|
||||
bg="white",
|
||||
fg=FG_COLOR,
|
||||
relief="flat",
|
||||
padx=15,
|
||||
pady=15,
|
||||
selectbackground=ACCENT_COLOR,
|
||||
highlightbackground=BG_COLOR
|
||||
)
|
||||
self.text1.config(state="disabled")
|
||||
self.text1.pack(fill='x', expand=True)
|
||||
|
||||
# 按钮
|
||||
frame_buttons = tk.Frame(parent, bg=BG_COLOR)
|
||||
frame_buttons.pack(pady=10, padx=20, fill='x')
|
||||
|
||||
for i in range(4):
|
||||
btn = WrappingButton(
|
||||
frame_buttons,
|
||||
text=f"选项 {i + 1}",
|
||||
command=partial(self.on_button_click, i),
|
||||
title_font=fonts.title,
|
||||
button_font=fonts.button,
|
||||
text_font=fonts.text,
|
||||
)
|
||||
self.buttons.append(btn)
|
||||
btn.pack(pady=6, fill='x')
|
||||
|
||||
# 确定按钮
|
||||
button5 = tk.Button(
|
||||
parent,
|
||||
text="确定",
|
||||
font=fonts.button,
|
||||
bg=ACCENT_COLOR,
|
||||
fg="white",
|
||||
command=self.on_ok_click,
|
||||
relief="flat",
|
||||
padx=20,
|
||||
pady=10,
|
||||
borderwidth=0,
|
||||
cursor="hand2"
|
||||
)
|
||||
button5.pack(pady=12)
|
||||
|
||||
def on_enter(e): button5.config(bg="#2980b9")
|
||||
|
||||
def on_leave(e): button5.config(bg=ACCENT_COLOR)
|
||||
|
||||
button5.bind("<Enter>", on_enter)
|
||||
button5.bind("<Leave>", on_leave)
|
||||
|
||||
# 解析区
|
||||
frame_bottom = tk.Frame(parent, bg=BG_COLOR)
|
||||
frame_bottom.pack(pady=10, padx=20, fill='both', expand=True)
|
||||
|
||||
self.text2 = tk.Text(
|
||||
frame_bottom,
|
||||
font=fonts.text,
|
||||
wrap='word',
|
||||
bg="white",
|
||||
fg="#555",
|
||||
relief="flat",
|
||||
padx=15,
|
||||
pady=15,
|
||||
spacing3=10,
|
||||
selectbackground=ACCENT_COLOR
|
||||
)
|
||||
self.text2.config(state="disabled")
|
||||
|
||||
scroll = tk.Scrollbar(frame_bottom, orient="vertical", command=self.text2.yview)
|
||||
self.text2.configure(yscrollcommand=scroll.set)
|
||||
self.text2.pack(side="left", fill='both', expand=True)
|
||||
scroll.pack(side="right", fill='y')
|
||||
|
||||
def _show_result(self, ):
|
||||
if self.row == None or len(self.row) < 15:
|
||||
return
|
||||
elif self.button_disable:
|
||||
return
|
||||
else:
|
||||
self.button_disable = True
|
||||
|
||||
self.flag = True
|
||||
|
||||
self.text2.config(state="normal")
|
||||
self.text2.delete("1.0", "end")
|
||||
self.text2.insert("end", self.row[14])
|
||||
self.text2.config(state="disabled")
|
||||
|
||||
correct = 0
|
||||
|
||||
for i in range(4):
|
||||
state = self.correct_answer[i] + 2 * self.my_answer[i] # 0:未选对, 1:漏选, 2:错选, 3:选对
|
||||
full_text = self.indexMap.get(self.shuffle_index[i]) + self.row[self.shuffle_index[i]]
|
||||
|
||||
if state == 1: # 漏选(正确但未选)→ 绿色
|
||||
self.buttons[i].config(text=full_text, style="Miss.TButton")
|
||||
elif state == 2: # 选错 → 红色
|
||||
self.buttons[i].config(text=full_text, style="Wrong.TButton")
|
||||
correct = 1
|
||||
elif state == 3: # 选对 → 淡绿色
|
||||
self.buttons[i].config(text=full_text, style="Correct.TButton")
|
||||
else: # 未选且错误 → 不变(保持原始或默认)
|
||||
self.buttons[i].config(text=full_text, style="Default.TButton")
|
||||
|
||||
self.c_backend.update(self.row[0], correct)
|
||||
self.winfo_toplevel().focus_set()
|
||||
|
||||
def init(self):
|
||||
self.my_answer = [0, 0, 0, 0]
|
||||
self.correct_answer = []
|
||||
self.flag = False
|
||||
self.button_disable = False
|
||||
|
||||
self.row = self.c_backend.get_question()
|
||||
|
||||
if self.row == None:
|
||||
print("没有了")
|
||||
return
|
||||
# 判断题处理
|
||||
elif self.row[6] == "" and self.row[7] == "":
|
||||
self.row[6] = "正确"
|
||||
self.row[7] = "错误"
|
||||
self.buttons[2].config(state="disabled")
|
||||
self.buttons[3].config(state="disabled")
|
||||
else:
|
||||
self.buttons[2].config(state="normal")
|
||||
self.buttons[3].config(state="normal")
|
||||
|
||||
# 更新题目
|
||||
self.text1.config(state="normal")
|
||||
self.text1.delete("1.0", "end")
|
||||
self.text1.insert("end", self.row[4] + "\n" + self.row[5])
|
||||
self.text1.config(state="disabled")
|
||||
|
||||
# 清空解析
|
||||
self.text2.config(state="normal")
|
||||
self.text2.delete("1.0", "end")
|
||||
self.text2.config(state="disabled")
|
||||
|
||||
# 混洗选项
|
||||
self.shuffle_index = [6, 7, 8, 9]
|
||||
random.shuffle(self.shuffle_index)
|
||||
|
||||
for i in range(4):
|
||||
btn_text = self.row[self.shuffle_index[i]].strip()
|
||||
self.buttons[i].config(
|
||||
text=f" {btn_text}",
|
||||
state="normal",
|
||||
style="Default.TButton"
|
||||
)
|
||||
self.correct_answer.append(1 if self.row[self.shuffle_index[i] + 4] == 1 else 0)
|
||||
self.winfo_toplevel().focus_set()
|
||||
|
||||
def on_button_click(self,button_number):
|
||||
if self.button_disable:
|
||||
return
|
||||
if sum(self.correct_answer) > 1:
|
||||
if self.my_answer[button_number] == 0:
|
||||
self.my_answer[button_number] = 1
|
||||
self.buttons[button_number].config(style="Selected.TButton")
|
||||
else:
|
||||
self.my_answer[button_number] = 0
|
||||
self.buttons[button_number].config(style="Default.TButton")
|
||||
else:
|
||||
self.my_answer[button_number] = 1
|
||||
self._show_result()
|
||||
self.winfo_toplevel().focus_set()
|
||||
|
||||
def on_ok_click(self):
|
||||
if self.flag:
|
||||
self.init()
|
||||
else:
|
||||
self. _show_result()
|
||||
self.winfo_toplevel().focus_set()
|
||||
|
||||
if __name__ == "__main__":
|
||||
from backend import Backend
|
||||
root = tk.Tk()
|
||||
font = Fonts()
|
||||
c_backend = Backend()
|
||||
app = Page0(root,font,c_backend)
|
||||
root.mainloop()
|
||||
105
components/page1.py
Normal file
105
components/page1.py
Normal file
@@ -0,0 +1,105 @@
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from components import *
|
||||
|
||||
|
||||
class Page1:
|
||||
def __init__(self, root, top_options, bottom_options):
|
||||
self.root = root
|
||||
self.top_options = top_options
|
||||
self.bottom_options = bottom_options
|
||||
|
||||
# ========== 顶部复选框区域 ==========
|
||||
self.top_frame = tk.Frame(self.root, bg=BG_COLOR, pady=25)
|
||||
self.top_frame.pack(side=tk.TOP, fill=tk.X)
|
||||
|
||||
self.top_vars = []
|
||||
for option in top_options:
|
||||
var = tk.BooleanVar(value=True)
|
||||
chk = ttk.Checkbutton(self.top_frame, text=option, variable=var, style="Custom.TCheckbutton")
|
||||
chk.pack(side=tk.LEFT, padx=20)
|
||||
self.top_vars.append(var)
|
||||
|
||||
# ========== 分割线 ==========
|
||||
separator = tk.Frame(self.root, height=1, bg="black")
|
||||
separator.pack(side=tk.TOP, fill=tk.X, padx=20)
|
||||
|
||||
# ========== 底部复选框区域 ==========
|
||||
self.bottom_frame = tk.Frame(self.root, bg=BG_COLOR, pady=25)
|
||||
self.bottom_frame.pack(side=tk.TOP, fill=tk.X)
|
||||
|
||||
self.bottom_vars = []
|
||||
for option in bottom_options:
|
||||
var = tk.BooleanVar(value=True)
|
||||
chk = ttk.Checkbutton(self.bottom_frame, text=option, variable=var, style="Custom.TCheckbutton")
|
||||
chk.pack(side=tk.LEFT, padx=20)
|
||||
self.bottom_vars.append(var)
|
||||
|
||||
# ========== 说明文本区域 ==========
|
||||
text_frame = tk.Frame(self.root, bg=BG_COLOR)
|
||||
text_frame.pack(side=tk.TOP, padx=40, pady=10, fill=tk.X)
|
||||
|
||||
text_widget = tk.Text(
|
||||
text_frame,
|
||||
height=6,
|
||||
width=60,
|
||||
wrap=tk.WORD,
|
||||
bg=BG_COLOR,
|
||||
fg=FG_COLOR,
|
||||
relief=tk.FLAT,
|
||||
borderwidth=0,
|
||||
font=(FONT_FAMILY, 12),
|
||||
padx=10,
|
||||
pady=10
|
||||
)
|
||||
text_widget.pack(expand=True, fill=tk.X)
|
||||
text_widget.insert(tk.END, instructions)
|
||||
text_widget.config(state=tk.DISABLED)
|
||||
|
||||
def setup_styles(self):
|
||||
"""设置 ttk 样式"""
|
||||
|
||||
|
||||
def top_selected(self):
|
||||
"""返回第一行中被选中的项"""
|
||||
return [opt for opt, var in zip(self.top_options, self.top_vars) if var.get()]
|
||||
|
||||
def bottom_selected(self):
|
||||
"""返回第二行中被选中的项"""
|
||||
return [opt for opt, var in zip(self.bottom_options, self.bottom_vars) if var.get()]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
top_options = ["1", "2", "3", "4"]
|
||||
bottom_options = ["全部的题", "未做过的题", "错过的题", ]
|
||||
|
||||
root = tk.Tk()
|
||||
app = Page1(root, top_options, bottom_options)
|
||||
|
||||
# 按钮区域
|
||||
button_frame = tk.Frame(root)
|
||||
button_frame.pack(side=tk.TOP, pady=20)
|
||||
|
||||
# 打印上一行选中项的按钮
|
||||
btn_print_top = tk.Button(
|
||||
button_frame,
|
||||
text="1",
|
||||
command=lambda: print(app.top_selected()),
|
||||
font=("Helvetica", 14),
|
||||
bg="lightblue",
|
||||
width=20
|
||||
)
|
||||
btn_print_top.pack(side=tk.LEFT, padx=10)
|
||||
|
||||
# 打印下一行选中项的按钮
|
||||
btn_print_bottom = tk.Button(
|
||||
button_frame,
|
||||
text="2",
|
||||
command=lambda: print(app.bottom_selected()),
|
||||
font=("Helvetica", 14),
|
||||
bg="lightgreen",
|
||||
width=20
|
||||
)
|
||||
btn_print_bottom.pack(side=tk.LEFT, padx=10)
|
||||
|
||||
root.mainloop()
|
||||
98
components/page2.py
Normal file
98
components/page2.py
Normal file
@@ -0,0 +1,98 @@
|
||||
# 统计数据页
|
||||
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
|
||||
class Page2:
|
||||
def __init__(self, root):
|
||||
self.root = root
|
||||
|
||||
# 创建主框架
|
||||
main_frame = ttk.Frame(root, padding="20")
|
||||
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
||||
main_frame.columnconfigure(0, weight=1)
|
||||
main_frame.rowconfigure(1, weight=1)
|
||||
|
||||
# 标题
|
||||
title_label = ttk.Label(main_frame, text="当前数据统计", font=("Arial", 16, "bold"))
|
||||
title_label.grid(row=0, column=0, columnspan=4, pady=(0, 20))
|
||||
|
||||
# === 关键指标区域 ===
|
||||
self.kpi_data = []
|
||||
|
||||
# KPI 容器
|
||||
self.kpi_container = ttk.Frame(main_frame)
|
||||
self.kpi_container.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
||||
for i in range(4):
|
||||
self.kpi_container.columnconfigure(i, weight=1)
|
||||
|
||||
self.value_labels = []
|
||||
self.create_dashboard_units_4col()
|
||||
|
||||
def create_kpi_unit(self, parent, name, value, row, col):
|
||||
"""创建单个单元"""
|
||||
unit_frame = ttk.Frame(parent, relief="solid", borderwidth=1, padding=10)
|
||||
unit_frame.grid(row=row, column=col, padx=8, pady=8, sticky="nsew")
|
||||
|
||||
# 设置行权重,使内容垂直居中
|
||||
parent.rowconfigure(row, weight=1)
|
||||
|
||||
if name and value:
|
||||
name_label = ttk.Label(unit_frame, text=f"{name}:", font=("Arial", 11))
|
||||
name_label.pack(anchor="w")
|
||||
value_label = ttk.Label(unit_frame, text=value, font=("Arial", 11, "bold"))
|
||||
value_label.pack(anchor="e")
|
||||
return value_label
|
||||
else:
|
||||
# 如果 name 或 value 为空,显示占位符或空白
|
||||
ttk.Label(unit_frame, text="—", foreground="gray").pack()
|
||||
return None
|
||||
|
||||
def create_dashboard_units_4col(self):
|
||||
"""创建4列布局的单元"""
|
||||
rows = (len(self.kpi_data) + 3) // 4 # 向上取整
|
||||
|
||||
for r in range(rows):
|
||||
self.kpi_container.rowconfigure(r, weight=1)
|
||||
|
||||
for idx, item in enumerate(self.kpi_data):
|
||||
row_idx = idx // 4
|
||||
col_idx = idx % 4
|
||||
|
||||
if item and len(item) >= 2:
|
||||
name, value = item[0], item[1]
|
||||
else:
|
||||
name, value = None, None
|
||||
value_label = self.create_kpi_unit(self.kpi_container, name, value, row_idx, col_idx)
|
||||
self.value_labels.clear()
|
||||
self.value_labels.append(value_label)
|
||||
|
||||
def update_data(self, new_values):
|
||||
self.kpi_data = new_values
|
||||
self.create_dashboard_units_4col()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import backend
|
||||
import random
|
||||
# 实例化后端
|
||||
c_backend = backend.Backend()
|
||||
|
||||
# 创建 GUI
|
||||
root = tk.Tk()
|
||||
root.title("统计面板")
|
||||
root.geometry("800x600")
|
||||
app = Page2(root)
|
||||
|
||||
def on_refresh():
|
||||
s = c_backend.get_statistics()
|
||||
for v in s:
|
||||
if v is not None and len(v) >= 2:
|
||||
v[1] = str(random.randint(1,100))
|
||||
app.update_data(s)
|
||||
|
||||
# 刷新按钮
|
||||
refresh_btn = ttk.Button(root, text="刷新数据", command=on_refresh)
|
||||
refresh_btn.grid(row=2, column=0, pady=10)
|
||||
|
||||
root.mainloop()
|
||||
Reference in New Issue
Block a user