commit c451fdf0e5178d90f1366b1e4ad5a910257e5687 Author: 王子恺 <1621362626@qq.com> Date: Wed Aug 20 16:25:51 2025 +0800 1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6caea1e --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/.idea/* +/data.db +/.old/* \ No newline at end of file diff --git a/backend.py b/backend.py new file mode 100644 index 0000000..aa9412f --- /dev/null +++ b/backend.py @@ -0,0 +1,195 @@ +import os +import random +import sqlite3 +import time +import zipfile + + +class Backend: + + def __init__(self, db="data.db"): + self.db_path = db + # 先解压 + if not os.path.exists(self.db_path) and os.path.exists(self.db_path + ".zip"): + with zipfile.ZipFile(self.db_path + ".zip", 'r') as zip_ref: + zip_ref.extractall(".") + + + self.time = time.time() + self.title_filter = self.get_source_type() + self.global_filter = [] + + + + def get_question(self): + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + # 构造 IN 子句的占位符 + placeholders = ','.join('?' for _ in self.title_filter) + + # 总权重查询 + total_query = f""" + SELECT SUM(count) + FROM questions + WHERE count > 0 AND title IN ({placeholders}) + """ + + #print(total_query) + #print(self.title_filter) + + cursor.execute(total_query, self.title_filter) + total_weights = cursor.fetchone()[0] + if total_weights is None or total_weights == 0: + conn.close() + return None + + # 生成随机数 [0, total_weights) + random_num = random.uniform(0, total_weights) + + # 主查询:加权随机选择 + query = f""" + SELECT * + FROM ( + SELECT *, + SUM(count) OVER (ORDER BY id ROWS UNBOUNDED PRECEDING) AS cum_weight + FROM questions + WHERE count > 0 AND title IN ({placeholders}) + ) + WHERE ? < cum_weight + ORDER BY cum_weight + LIMIT 1; + """ + + params = tuple(self.title_filter) + (random_num,) + cursor.execute(query, params) + + row = cursor.fetchone() + print(total_weights, row) + + conn.close() + self.time = time.time() + + return list(row) if row else None + + def update(self, id, state): + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + # 作对-1 错了+2 + if state != 0: + query = """ + UPDATE questions + SET count = count + 2 + WHERE id = ? + and count > 0""" + else: + query = """ + UPDATE questions + SET count = count - 1 + WHERE id = ? + """ + cursor.execute(query, (id,)) + + # 记录用时 + query = "INSERT INTO answers_history (id, time_used, state) VALUES (?, ?, ?)" + cursor.execute(query, (id, time.time() - self.time, state)) + + conn.commit() + conn.close() + + def reset_time(self): + self.time = time.time() + + def get_acc(self, top_n=0): + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + try: + if top_n > 0: + query = """ + SELECT CAST(SUM(CASE WHEN state = 0 THEN 1 ELSE 0 END) AS FLOAT) / COUNT(*) AS accuracy + FROM (SELECT state + FROM answers_history + ORDER BY time DESC + LIMIT ?) + """ + params = (top_n,) + else: + query = """ + SELECT CAST(SUM(CASE WHEN state = 0 THEN 1 ELSE 0 END) AS FLOAT) / COUNT(*) AS accuracy FROM answers_history + """ + params = () + + cursor.execute(query, params) + result = cursor.fetchone()[0] + return result * 100 if result is not None else 0.0 + + except sqlite3.Error as e: + print(f"数据库错误:{e}") + return 0.0 + finally: + conn.close() + + def get_avg_time(self, top_n=0): + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + try: + if top_n > 0: + query = """ + SELECT CAST(SUM(time_used) AS FLOAT) / COUNT(*) AS accuracy + FROM (SELECT time_used + FROM answers_history + where time_used is not null + ORDER BY time DESC + LIMIT ?) + """ + params = (top_n,) + else: + query = """ + SELECT CAST(SUM(time_used) AS FLOAT) / COUNT(*) AS accuracy FROM answers_history where time_used is not null + """ + params = () + + cursor.execute(query, params) + result = cursor.fetchone()[0] + return result if result is not None else 0.0 + + except sqlite3.Error as e: + print(f"数据库错误:{e}") + return 0.0 + finally: + conn.close() + + def get_statistics(self): + avg_all = self.get_avg_time() + avg_50 = self.get_avg_time(50) + avg_100 = self.get_avg_time(100) + + result = [ + ["正确率", f"{self.get_acc():.1f}/60%"], + [], + ["最近50题正确率", f"{self.get_acc(50):.1f}/60%"], + ["最近100题正确率", f"{self.get_acc(120):.1f}/60%"], + ["平均耗时", f"{avg_all:.1f}/72s"], + ["预计做完用时", f"{avg_all * 1.667:.1f}/120min"], + ["50平均耗时", f"{avg_50:.1f}/72s"], + ["预计做完用时", f"{avg_50 * 1.667:.1f}/120min"], + ["120平均耗时", f"{avg_100:.1f}/72s"], + ["预计做完用时", f"{avg_100 * 1.667:.1f}/120min"], + ] + return result + + def get_source_type(self): + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + query = "SELECT DISTINCT title FROM questions" + cursor.execute(query) + result = cursor.fetchall() + new_list = [item[0] for item in result] + return new_list + + def set_config(self,a,b): + self.title_filter = a + self.global_filter = b diff --git a/components/__init__.py b/components/__init__.py new file mode 100644 index 0000000..4b72bbd --- /dev/null +++ b/components/__init__.py @@ -0,0 +1,4 @@ +from .constants import * +from .page0 import Page0 +from .page1 import Page1 +from .page2 import Page2 diff --git a/components/constants.py b/components/constants.py new file mode 100644 index 0000000..304ec7f --- /dev/null +++ b/components/constants.py @@ -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) diff --git a/components/page0.py b/components/page0.py new file mode 100644 index 0000000..c75524a --- /dev/null +++ b/components/page0.py @@ -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("", lambda e: command() if command else None) + self.label.bind("", lambda e: command() if command else None) + + # 绑定鼠标悬停事件 + self.bind("", self._on_enter) + self.bind("", self._on_leave) + self.label.bind("", self._on_enter) + self.label.bind("", 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("", on_enter) + button5.bind("", 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() diff --git a/components/page1.py b/components/page1.py new file mode 100644 index 0000000..a0781c9 --- /dev/null +++ b/components/page1.py @@ -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() diff --git a/components/page2.py b/components/page2.py new file mode 100644 index 0000000..bbf3fae --- /dev/null +++ b/components/page2.py @@ -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() diff --git a/crawler/getLeCheng_chapter.ipynb b/crawler/getLeCheng_chapter.ipynb new file mode 100644 index 0000000..669fb0a --- /dev/null +++ b/crawler/getLeCheng_chapter.ipynb @@ -0,0 +1,388 @@ +{ + "cells": [ + { + "cell_type": "code", + "id": "initial_id", + "metadata": { + "collapsed": true, + "ExecuteTime": { + "end_time": "2025-08-19T01:03:22.758571Z", + "start_time": "2025-08-19T01:03:22.753008Z" + } + }, + "source": [ + "import time\n", + "\n", + "from selenium import webdriver\n", + "from selenium.webdriver.edge.service import Service\n", + "from selenium.webdriver.common.by import By\n", + "from selenium.webdriver.support.ui import WebDriverWait\n", + "from selenium.webdriver.support import expected_conditions as EC\n", + "from selenium.webdriver.edge.options import Options" + ], + "outputs": [], + "execution_count": 49 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-08-19T01:03:23.224371Z", + "start_time": "2025-08-19T01:03:23.220216Z" + } + }, + "cell_type": "code", + "source": [ + "from bs4 import BeautifulSoup\n", + "import sqlite3" + ], + "id": "59b26d9f105eae85", + "outputs": [], + "execution_count": 50 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-08-19T01:03:25.179818Z", + "start_time": "2025-08-19T01:03:25.173558Z" + } + }, + "cell_type": "code", + "source": "db_path = '../data.db'", + "id": "37a70656848ceced", + "outputs": [], + "execution_count": 51 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-08-19T01:03:25.713012Z", + "start_time": "2025-08-19T01:03:25.704775Z" + } + }, + "cell_type": "code", + "source": [ + "conn = sqlite3.connect(db_path)\n", + "conn.execute('''CREATE TABLE \"questions\"\n", + "(\n", + " id INTEGER\n", + " constraint questions_pk\n", + " primary key autoincrement,\n", + " title TEXT,\n", + " chapter TEXT,\n", + " q_num text,\n", + " q_type text,\n", + " question TEXT not null,\n", + " a TEXT not null,\n", + " b TEXT not null,\n", + " c TEXT not null,\n", + " d TEXT not null,\n", + " a_result BLOB default false,\n", + " b_result BLOB default false,\n", + " c_result BLOB default false,\n", + " d_result BLOB default false,\n", + " explanation TEXT,\n", + " count integer default 3 not null\n", + ")''')\n", + "\n", + "conn.execute('''CREATE TABLE \"answers_history\"\n", + "(\n", + " id INTEGER not null\n", + " constraint answers_history__questions_id_fk\n", + " references questions,\n", + " time_used INTEGER,\n", + " state INTEGER,\n", + " time text default CURRENT_TIMESTAMP\n", + ")''')\n", + "\n", + "conn.execute('''CREATE TABLE url\n", + " (\n", + " id INTEGER not null,\n", + " url TEXT\n", + " )''')\n", + "\n", + "\n", + "conn.commit()\n" + ], + "id": "d70a270099e8b056", + "outputs": [], + "execution_count": 52 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-08-19T01:03:27.430817Z", + "start_time": "2025-08-19T01:03:27.423603Z" + } + }, + "cell_type": "code", + "source": [ + "edge_options = Options()\n", + "#edge_options.add_argument(\"--headless\") # 可选:无界面模式\n", + "edge_options.add_argument(\"--disable-gpu\")\n", + "edge_options.add_argument(\"--no-sandbox\")\n", + "edge_options.add_argument(\"--disable-extensions\")\n", + "edge_options.add_argument(\"--disable-plugins\")\n", + "edge_options.add_argument(\"--disable-popup-blocking\")\n", + "edge_options.add_argument(\"--disable-infobars\")\n", + "edge_options.add_argument(\"--disable-notifications\")\n", + "edge_options.add_argument(\"--no-first-run\")\n", + "edge_options.add_argument(\"--no-default-browser-check\")\n", + "\n", + "user_data_dir = r\"D:\\code\\edge\"\n", + "edge_options.add_argument(f\"--user-data-dir={user_data_dir}\")\n", + "# 指定配置文件(可选,默认是 Default)\n", + "edge_options.add_argument(\"--profile-directory=Default\")" + ], + "id": "e4a35062c4549f44", + "outputs": [], + "execution_count": 53 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-08-19T01:03:30.978615Z", + "start_time": "2025-08-19T01:03:28.414779Z" + } + }, + "cell_type": "code", + "source": [ + "# 指定 EdgeDriver 路径(可选,若已配置环境变量可省略)\n", + "service = Service(executable_path=r\"D:\\app\\edgeDriver\\msedgedriver.exe\")\n", + "# 创建 Edge 浏览器实例\n", + "driver = webdriver.Edge(service=service, options=edge_options)" + ], + "id": "9b48ddaca80598aa", + "outputs": [], + "execution_count": 54 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-08-19T01:00:42.177993Z", + "start_time": "2025-08-19T01:00:42.171173Z" + } + }, + "cell_type": "code", + "source": [ + "def get_web(url):\n", + " driver.get(url)\n", + "\n", + " # 等待页面渲染完成(例如等待 body 加载)\n", + " wait = WebDriverWait(driver, 720)\n", + " wait.until(EC.presence_of_element_located((By.TAG_NAME, \"body\")))\n", + " time.sleep(3)\n", + "\n", + " #进入背题模式\n", + " clickable_element = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, \".setting-type.iconfont.icon-setting\")))\n", + " clickable_element.click()\n", + " wait.until(\n", + " EC.element_to_be_clickable((By.CSS_SELECTOR, \".question-setting-button.ant-btn.ant-btn-default\"))).click()\n", + "\n", + " # 获取渲染后的 HTML\n", + " rendered_html = driver.page_source\n", + " return rendered_html" + ], + "id": "2b02063fec8abbdd", + "outputs": [], + "execution_count": 43 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-08-19T01:00:42.212116Z", + "start_time": "2025-08-19T01:00:42.206610Z" + } + }, + "cell_type": "code", + "source": [ + "def list_get(lst, index, default=\"\"):\n", + " try:\n", + " return lst[index]\n", + " except IndexError:\n", + " return default" + ], + "id": "de9650bb0e005d4a", + "outputs": [], + "execution_count": 44 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-08-19T01:00:42.247110Z", + "start_time": "2025-08-19T01:00:42.237114Z" + } + }, + "cell_type": "code", + "source": [ + "def write2db(rendered_html, info):\n", + " # 解析web并登记\n", + " soup = BeautifulSoup(rendered_html, 'html.parser')\n", + " full_left = soup.find_all('div', class_='pull-left')\n", + " for questions in full_left:\n", + " for question in questions.children:\n", + " title_info = [] #num,type,question\n", + " answers_info = []\n", + " answers_correct_info = []\n", + " explain_info = \"\"\n", + "\n", + " # 标题信息\n", + " for title in question.find_all('div', class_='p-stem'):\n", + " for element in title.children:\n", + " title_info.append(element.text)\n", + "\n", + " # 题目信息\n", + " for answer in question.find_all('div', class_='answer-ul'):\n", + " for element in answer.find_all(\"div\", recursive=False):\n", + " # 答案\n", + " if \"answer\" in element.get(\"class\"):\n", + " answers_correct_info.append(True)\n", + " else:\n", + " answers_correct_info.append(False)\n", + "\n", + " # 问题\n", + " text_elements = element.select(\"div > div > div > div > p\")\n", + " for text_element in text_elements:\n", + " if text_element.text is not None and text_element.text != \"\":\n", + " answers_info.append(text_element.text)\n", + "\n", + " # 解析\n", + " for explain in question.find_all('div', class_='practise-answer-text'):\n", + " explain_info += str(explain.get_text(strip=True))\n", + "\n", + " cursor = conn.execute(\n", + " \"INSERT INTO questions (title, chapter, q_num, q_type, question, a, b, c, d, a_result, b_result, c_result, d_result, explanation) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\",\n", + " (\n", + " info[1],\n", + " info[2],\n", + " list_get(title_info,0),\n", + " list_get(title_info,1),\n", + " list_get(title_info,2),\n", + " list_get(answers_info,0),\n", + " list_get(answers_info,1),\n", + " list_get(answers_info,2),\n", + " list_get(answers_info,3),\n", + " list_get(answers_correct_info,0,False),\n", + " list_get(answers_correct_info,1,False),\n", + " list_get(answers_correct_info,2,False),\n", + " list_get(answers_correct_info,3,False),\n", + " explain_info,\n", + " )\n", + " )\n", + " inserted_id = cursor.lastrowid\n", + " conn.execute(\n", + " \"INSERT INTO url (id, url) VALUES (?, ?)\",\n", + " (inserted_id, info[0], )\n", + " )\n", + " conn.commit()" + ], + "id": "c28a23cbd84f6ea0", + "outputs": [], + "execution_count": 45 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-08-19T01:00:42.273569Z", + "start_time": "2025-08-19T01:00:42.265569Z" + } + }, + "cell_type": "code", + "source": [ + "bg_infos = [\n", + " [\"期货乐橙章节练习\", 1, 1414, 1],\n", + " [\"期货乐橙章节练习\", 2, 1419, 3],\n", + " [\"期货乐橙章节练习\", 3, 1448, 2],\n", + " [\"期货乐橙章节练习\", 4, 1485, 2],\n", + " [\"期货乐橙章节练习\", 5, 1523, 2],\n", + " [\"期货乐橙章节练习\", 6, 1543, 2],\n", + "]" + ], + "id": "f8ed3be15b2a69a7", + "outputs": [], + "execution_count": 46 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-08-19T01:00:42.311569Z", + "start_time": "2025-08-19T01:00:42.302568Z" + } + }, + "cell_type": "code", + "source": [ + "def main():\n", + " try:\n", + " for bg_info in bg_infos:\n", + " for index in range(1, bg_info[3]+1):\n", + " url = f\"https://www.bestlec.com/practise/practise?title=%E9%A1%BA%E5%BA%8F%E7%BB%83%E4%B9%A0&qBankId=39&qBankTitle=%E3%80%90%E6%9C%9F%E8%B4%A7%E6%B3%95%E8%A7%84%E3%80%91%E7%AB%A0%E8%8A%82%E7%BB%83%E4%B9%A0&chapterId={bg_info[2]}&practise=1&type=practise&selectSec={index}\"\n", + " rendered_html = get_web(url)\n", + " write2db(rendered_html, [url, bg_info[0], bg_info[1]])\n", + " except Exception as e:\n", + " print(\"error: \" + e)\n", + " finally:\n", + " try:\n", + " conn.close()\n", + " except Exception as e:\n", + " print(\"db:\", e)\n", + "\n", + " try:\n", + " driver.quit()\n", + " except Exception as e:\n", + " print(\"brother:\", e)" + ], + "id": "fcfc560b46c29aaa", + "outputs": [], + "execution_count": 47 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-08-19T01:01:51.670165Z", + "start_time": "2025-08-19T01:00:42.337618Z" + } + }, + "cell_type": "code", + "source": [ + "if __name__ == '__main__':\n", + " main()" + ], + "id": "811c9d3647c46f8b", + "outputs": [], + "execution_count": 48 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-08-19T01:01:51.740128Z", + "start_time": "2025-08-19T01:01:51.737199Z" + } + }, + "cell_type": "code", + "source": "", + "id": "5224515d66fe0b", + "outputs": [], + "execution_count": null + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/crawler/getLeCheng_exam.ipynb b/crawler/getLeCheng_exam.ipynb new file mode 100644 index 0000000..1e2ea6e --- /dev/null +++ b/crawler/getLeCheng_exam.ipynb @@ -0,0 +1,415 @@ +{ + "cells": [ + { + "cell_type": "code", + "id": "initial_id", + "metadata": { + "collapsed": true, + "ExecuteTime": { + "end_time": "2025-08-19T02:05:36.929261Z", + "start_time": "2025-08-19T02:05:36.924419Z" + } + }, + "source": [ + "import time\n", + "\n", + "from selenium import webdriver\n", + "from selenium.webdriver.edge.service import Service\n", + "from selenium.webdriver.common.by import By\n", + "from selenium.webdriver.support.ui import WebDriverWait\n", + "from selenium.webdriver.support import expected_conditions as EC\n", + "from selenium.webdriver.edge.options import Options" + ], + "outputs": [], + "execution_count": 86 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-08-19T02:05:37.477084Z", + "start_time": "2025-08-19T02:05:37.467743Z" + } + }, + "cell_type": "code", + "source": [ + "from bs4 import BeautifulSoup\n", + "import sqlite3" + ], + "id": "59b26d9f105eae85", + "outputs": [], + "execution_count": 87 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-08-19T02:05:38.051715Z", + "start_time": "2025-08-19T02:05:38.047420Z" + } + }, + "cell_type": "code", + "source": "db_path = '../data.db'", + "id": "37a70656848ceced", + "outputs": [], + "execution_count": 88 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-08-19T02:05:38.522754Z", + "start_time": "2025-08-19T02:05:38.515572Z" + } + }, + "cell_type": "code", + "source": [ + "conn = sqlite3.connect(db_path)\n", + "conn.execute('''CREATE TABLE \"questions\"\n", + "(\n", + " id INTEGER\n", + " constraint questions_pk\n", + " primary key autoincrement,\n", + " title TEXT,\n", + " chapter TEXT,\n", + " q_num text,\n", + " q_type text,\n", + " question TEXT not null,\n", + " a TEXT not null,\n", + " b TEXT not null,\n", + " c TEXT not null,\n", + " d TEXT not null,\n", + " a_result BLOB default false,\n", + " b_result BLOB default false,\n", + " c_result BLOB default false,\n", + " d_result BLOB default false,\n", + " explanation TEXT,\n", + " count integer default 3 not null\n", + ")''')\n", + "\n", + "conn.execute('''CREATE TABLE \"answers_history\"\n", + "(\n", + " id INTEGER not null\n", + " constraint answers_history__questions_id_fk\n", + " references questions,\n", + " time_used INTEGER,\n", + " state INTEGER,\n", + " time text default CURRENT_TIMESTAMP\n", + ")''')\n", + "\n", + "conn.execute('''CREATE TABLE url\n", + " (\n", + " id INTEGER not null,\n", + " url TEXT\n", + " )''')\n", + "\n", + "\n", + "conn.commit()\n" + ], + "id": "d70a270099e8b056", + "outputs": [], + "execution_count": 89 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-08-19T02:05:39.660112Z", + "start_time": "2025-08-19T02:05:39.654188Z" + } + }, + "cell_type": "code", + "source": [ + "edge_options = Options()\n", + "#edge_options.add_argument(\"--headless\") # 可选:无界面模式\n", + "edge_options.add_argument(\"--disable-gpu\")\n", + "edge_options.add_argument(\"--no-sandbox\")\n", + "edge_options.add_argument(\"--disable-extensions\")\n", + "edge_options.add_argument(\"--disable-plugins\")\n", + "edge_options.add_argument(\"--disable-popup-blocking\")\n", + "edge_options.add_argument(\"--disable-infobars\")\n", + "edge_options.add_argument(\"--disable-notifications\")\n", + "edge_options.add_argument(\"--no-first-run\")\n", + "edge_options.add_argument(\"--no-default-browser-check\")\n", + "\n", + "user_data_dir = r\"D:\\code\\edge\"\n", + "edge_options.add_argument(f\"--user-data-dir={user_data_dir}\")\n", + "# 指定配置文件(可选,默认是 Default)\n", + "edge_options.add_argument(\"--profile-directory=Default\")" + ], + "id": "e4a35062c4549f44", + "outputs": [], + "execution_count": 90 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-08-19T02:05:42.646764Z", + "start_time": "2025-08-19T02:05:40.137721Z" + } + }, + "cell_type": "code", + "source": [ + "# 指定 EdgeDriver 路径(可选,若已配置环境变量可省略)\n", + "service = Service(executable_path=r\"D:\\app\\edgeDriver\\msedgedriver.exe\")\n", + "# 创建 Edge 浏览器实例\n", + "driver = webdriver.Edge(service=service, options=edge_options)" + ], + "id": "9b48ddaca80598aa", + "outputs": [], + "execution_count": 91 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-08-19T01:58:29.792348Z", + "start_time": "2025-08-19T01:58:29.786681Z" + } + }, + "cell_type": "code", + "source": [ + "def get_web(url):\n", + " driver.get(url)\n", + "\n", + " # 等待页面渲染完成(例如等待 body 加载)\n", + " wait = WebDriverWait(driver, 720)\n", + " wait.until(EC.presence_of_element_located((By.TAG_NAME, \"body\")))\n", + " time.sleep(3)\n", + "\n", + " #进入背题模式\n", + " # clickable_element = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, \".setting-type.iconfont.icon-setting\")))\n", + " # clickable_element.click()\n", + " # wait.until(\n", + " # EC.element_to_be_clickable((By.CSS_SELECTOR, \".question-setting-button.ant-btn.ant-btn-default\"))).click()\n", + "\n", + " # 获取渲染后的 HTML\n", + " rendered_html = driver.page_source\n", + " return rendered_html" + ], + "id": "2b02063fec8abbdd", + "outputs": [], + "execution_count": 80 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-08-19T01:58:30.323362Z", + "start_time": "2025-08-19T01:58:30.317695Z" + } + }, + "cell_type": "code", + "source": [ + "def list_get(lst, index, default=\"\"):\n", + " try:\n", + " return lst[index]\n", + " except IndexError:\n", + " return default" + ], + "id": "de9650bb0e005d4a", + "outputs": [], + "execution_count": 81 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-08-19T01:58:30.884914Z", + "start_time": "2025-08-19T01:58:30.871279Z" + } + }, + "cell_type": "code", + "source": [ + "def write2db(rendered_html, info):\n", + " # 解析web并登记\n", + " soup = BeautifulSoup(rendered_html, 'html.parser')\n", + " full_left = soup.find_all('div', class_='pull-left')\n", + "\n", + " for questions in full_left:\n", + " for question in questions.children:\n", + " title_info = [] #num,type,question\n", + " answers_info = []\n", + " answers_correct_info = [False,False,False,False,]\n", + " explain_info = \"\"\n", + "\n", + " # 标题信息\n", + " for title in question.find_all('div', class_='p-stem'):\n", + " for element in title.children:\n", + " title_info.append(element.text)\n", + "\n", + "\n", + " # 题目信息\n", + " for answer in question.find_all('div', class_='answerClass'):\n", + " # 答案\n", + "\n", + "\n", + " # 带选项\n", + " text_elements = answer.select(\"div > div > p\")\n", + " for text_element in text_elements:\n", + " if text_element.text is not None and text_element.text != \"\":\n", + " answers_info.append(text_element.text)\n", + "\n", + "\n", + " # 解析\n", + " explains = question.find_all('div', class_='practise-answer-text')\n", + "\n", + " try:\n", + " for i in explains[0].text:\n", + " match i:\n", + " case \"A\":\n", + " answers_correct_info[0] = True\n", + " case \"B\":\n", + " answers_correct_info[1] = True\n", + " case \"C\":\n", + " answers_correct_info[2] = True\n", + " case \"D\":\n", + " answers_correct_info[3] = True\n", + " case _ :\n", + " print(i)\n", + " pass\n", + " except Exception as e:\n", + " # e.with_traceback()\n", + " # print(title_info)\n", + " pass\n", + "\n", + " if len(explains[0]) == 0:\n", + " print(\"0 answers found!!\", title_info)\n", + "\n", + " for explain in explains:\n", + " explain_info += str(explain.get_text(strip=True))\n", + "\n", + "\n", + " cursor = conn.execute(\n", + " \"INSERT INTO questions (title, chapter, q_num, q_type, question, a, b, c, d, a_result, b_result, c_result, d_result, explanation) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\",\n", + " (\n", + " info[1],\n", + " info[2],\n", + " list_get(title_info,0),\n", + " list_get(title_info,1),\n", + " list_get(title_info,2),\n", + " list_get(answers_info,0),\n", + " list_get(answers_info,1),\n", + " list_get(answers_info,2),\n", + " list_get(answers_info,3),\n", + " list_get(answers_correct_info,0,False),\n", + " list_get(answers_correct_info,1,False),\n", + " list_get(answers_correct_info,2,False),\n", + " list_get(answers_correct_info,3,False),\n", + " explain_info,\n", + " )\n", + " )\n", + " inserted_id = cursor.lastrowid\n", + " conn.execute(\n", + " \"INSERT INTO url (id, url) VALUES (?, ?)\",\n", + " (inserted_id, info[0], )\n", + " )\n", + " conn.commit()" + ], + "id": "c28a23cbd84f6ea0", + "outputs": [], + "execution_count": 82 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-08-19T01:58:31.409810Z", + "start_time": "2025-08-19T01:58:31.404300Z" + } + }, + "cell_type": "code", + "source": [ + "ttt = \"期货乐橙假题\"\n", + "bg_infos = [\n", + " [ttt, 0, 1385, 1],\n", + " [ttt, 0, 1392, 1],\n", + " [ttt, 0, 1393, 1],\n", + " [ttt, 0, 1394, 1],\n", + " [ttt, 0, 1395, 1],\n", + "]" + ], + "id": "f8ed3be15b2a69a7", + "outputs": [], + "execution_count": 83 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-08-19T01:58:32.106767Z", + "start_time": "2025-08-19T01:58:32.099456Z" + } + }, + "cell_type": "code", + "source": [ + "def main():\n", + " try:\n", + " for bg_info in bg_infos:\n", + " for index in range(1, bg_info[3]+1):\n", + " #url = f\"https://www.bestlec.com/practise/practise?title=%E9%A1%BA%E5%BA%8F%E7%BB%83%E4%B9%A0&qBankId=39&qBankTitle=%E3%80%90%E6%9C%9F%E8%B4%A7%E6%B3%95%E8%A7%84%E3%80%91%E7%AB%A0%E8%8A%82%E7%BB%83%E4%B9%A0&chapterId={bg_info[2]}&practise=1&type=practise&selectSec={index}\"\n", + "\n", + " url = f\"https://www.bestlec.com/practise/practise?id={bg_info[2]}&qBankTitle=%E3%80%90%E6%9C%9F%E8%B4%A7%E6%B3%95%E8%A7%84%E3%80%91%E5%8E%86%E5%B9%B4%E7%9C%9F%E9%A2%98&title=%E7%9C%9F%E9%A2%98%E8%80%83%E8%AF%95&type=test&testType=search_paper\"\n", + "\n", + " rendered_html = get_web(url)\n", + " write2db(rendered_html, [url, bg_info[0], bg_info[1]])\n", + " except Exception as e:\n", + " print(\"error: \" + e)\n", + " finally:\n", + " try:\n", + " conn.close()\n", + " except Exception as e:\n", + " print(\"db:\", e)\n", + "\n", + " try:\n", + " driver.quit()\n", + " except Exception as e:\n", + " print(\"brother:\", e)" + ], + "id": "fcfc560b46c29aaa", + "outputs": [], + "execution_count": 84 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-08-19T01:59:02.604021Z", + "start_time": "2025-08-19T01:58:32.772829Z" + } + }, + "cell_type": "code", + "source": [ + "if __name__ == '__main__':\n", + " main()" + ], + "id": "811c9d3647c46f8b", + "outputs": [], + "execution_count": 85 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-08-19T01:59:03.138625Z", + "start_time": "2025-08-19T01:59:03.135518Z" + } + }, + "cell_type": "code", + "source": "", + "id": "5224515d66fe0b", + "outputs": [], + "execution_count": null + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/data.db.zip b/data.db.zip new file mode 100644 index 0000000..d31e396 Binary files /dev/null and b/data.db.zip differ diff --git a/main.py b/main.py new file mode 100644 index 0000000..1bc6b26 --- /dev/null +++ b/main.py @@ -0,0 +1,91 @@ +import tkinter as tk +from tkinter import ttk +from backend import Backend +from components import * + +# 后端 +c_backend = Backend() + +# 创建主窗口 +root = tk.Tk() +root.title("zrx") +root.geometry("1000x900") +root.configure(bg=BG_COLOR) + +# 字体文件 +fonts = Fonts() + +notebook = ttk.Notebook(root) +notebook.pack(fill='both', expand=True, padx=10, pady=10) + +style = ttk.Style() +style.theme_use("winnative") + +# 自定义复选框样式 +style.configure( + "Custom.TCheckbutton", + font=(FONT_FAMILY, 14), + background=BG_COLOR, + foreground=FG_COLOR, + padding=8 +) +style.map( + "Custom.TCheckbutton", + background=[("active", BG_COLOR)], + foreground=[("active", ACCENT_COLOR)] +) + +# 页面0 +frame0 = ttk.Frame(notebook, padding=20) +notebook.add(frame0, text="主页") +page0 = Page0(frame0,fonts,c_backend) +page0.init() + +# 页面1 +frame1 = ttk.Frame(notebook, padding=20) +notebook.add(frame1, text="设置") +top_options = c_backend.get_source_type() +page1 = Page1(frame1,top_options,bottom_options) + +# 页面2 +frame2 = ttk.Frame(notebook, padding=20) +notebook.add(frame2, text="统计") +page2 = Page2(frame2) + +# 事件 +def page0_event(): + c_backend.reset_time() + c_backend.set_config( + page1.top_selected(), + page1.bottom_selected() + ) + +def page1_event(): + pass + +def page2_event(): + r = c_backend.get_statistics() + print(r) + page2.update_data(r) + +tab_handlers = [ + page0_event, + page1_event, + page2_event, +] + +def on_space_pressed(event): + page0.init() + +def on_tab_changed(event): + try: + tab_handlers[notebook.index("current")]() + except Exception as e: + print(e) + +# 绑定事件 +root.bind('', on_space_pressed) +notebook.bind("<>", on_tab_changed) + +# 运行主循环 +root.mainloop()