当前位置:首页>python>监考大屏系统 - Python GUI 版本

监考大屏系统 - Python GUI 版本

  • 2026-06-28 00:51:39
监考大屏系统 - Python GUI 版本

📚 监考大屏系统 - Python GUI 版本

概述

本系统是基于原 H5 监考大屏页面的 Python 桌面版本实现,使用 tkinter + ttkbootstrap 构建, 提供考试管理、实时倒计时监控大屏、通知推送等核心功能,适合学校机房或教室部署使用。


环境要求

依赖
说明
Python 3.8+
基础运行环境
tkinter
Python 标准库(通常自带)
ttkbootstrap(可选)
提供更美观的主题,未安装时自动降级为原生 tkinter

安装 ttkbootstrap(推荐)

pip install ttkbootstrap

启动

python monitor_gui.py

功能模块

1. 考试列表管理

  • 表格展示所有考试(名称、学校、科目数、状态、创建时间)
  • 支持关键词搜索过滤
  • 状态自动判定:等待开始 / 进行中 / 已结束
  • 操作:进入大屏、编辑、发通知、删除

2. 考试编辑/新建

  • 填写考试名称、学校名称
  • 添加多个科目,每个科目包含:
    • 科目名称
    • 开始时间(格式:2026-06-25T08:30
    • 结束时间
    • 听力音频文件路径(可选)
    • 音频播放时间(可选)
  • 动态增删科目行
  • 数据持久化到本地 JSON 文件

3. 监控大屏

  • 深色全屏风格显示
  • 实时倒计时(每秒刷新)
  • 圆形进度环可视化剩余时间
  • 自动切换科目(当前科目结束后显示下一场)
  • 状态提示:等待开始 → 考试进行中 → 已结束 → 全部结束
  • 显示当前时间、考试时间段
  • 语音提醒节点(开始、剩余15分钟,输出到控制台)

4. 通知系统

  • 对指定考试发送通知
  • 设置通知显示时长(秒)
  • 大屏模式下弹窗显示通知内容
  • 通知历史记录(最近10条)
  • 自动超时关闭

5. 主题切换

支持 ttkbootstrap 内置主题:

  • cosmo(默认,明亮)
  • darkly(暗色)
  • journal(暖色)
  • flatly(扁平)
  • superhero(深蓝)

数据存储

所有数据持久化在程序同目录下的 JSON 文件中:

文件
内容
exam_data.json
考试列表及科目信息
exam_notifications.json
通知历史记录
exam_settings.json
用户设置(主题等)

与 H5 版本功能对照

功能
H5 版本
Python GUI 版本
考试 CRUD
科目时间管理
大屏倒计时
圆形进度环
✅ SVG
✅ Canvas Arc
主题切换
✅ 浅/深/卡通
✅ ttkbootstrap 多主题
通知发送
通知弹窗
✅ Toplevel 窗口
通知历史
语音播报
✅ Web Speech API
⚠️ 控制台输出(可扩展 pyttsx3)
听力音频播放
✅ HTML5 Audio
⚠️ 路径记录(可扩展 pygame)
数据持久化
localStorage
JSON 文件
复制链接/快捷方式
❌ 桌面版无需

扩展建议

  1. 语音播报:安装 pyttsx3,替换 print("[语音]") 为 TTS 调用
  2. 音频播放:安装 pygame,在对应时间点触发 pygame.mixer.music.play()
  3. 全屏模式:调用 root.attributes("-fullscreen", True) 实现教室投屏
  4. 网络同步:可增加 WebSocket 或 HTTP 接口,支持多终端同步通知

截图说明

程序启动后默认显示考试列表页,通过顶部标签页切换:

  • 📋 考试列表:管理所有考试
  • ✏️ 编辑考试:新建/修改考试信息
  • 🖥️ 监控大屏:全屏倒计时展示

许可

本项目仅供教学和内部使用。

#!/usr/bin/env python3-- coding: utf-8 --"""监考大屏系统 - Python GUI 版本基于 tkinter + ttkbootstrap 实现,功能对标 H5 版本"""import jsonimport osimport timeimport threadingimport uuidfrom datetime import datetime, timedeltafrom pathlib import Pathtry:import ttkbootstrap as ttkfrom ttkbootstrap.constants import *from ttkbootstrap.dialogs import Messagebox, Queryboxfrom ttkbootstrap.scrolled import ScrolledFrameimport tkinter as tkHAS_BOOTSTRAP = Trueexcept ImportError:import tkinter as tkfrom tkinter import ttk, messageboxHAS_BOOTSTRAP = Falseimport tkinter.filedialog as filedialog==================== 数据层 ====================DATA_FILE = Path(file).parent / "exam_data.json"NOTIF_FILE = Path(file).parent / "exam_notifications.json"SETTINGS_FILE = Path(file).parent / "exam_settings.json"def load_json(path, default=None):if default is None:default = {}try:if path.exists():with open(path, "r", encoding="utf-8"as f:return json.load(f)except Exception:passreturn defaultdef save_json(path, data):with open(path, "w", encoding="utf-8"as f:json.dump(data, f, ensure_ascii=False, indent=2)def generate_id():return uuid.uuid4().hex[:12]def format_datetime(dt_str):"""格式化日期时间字符串"""try:dt = datetime.fromisoformat(dt_str)return dt.strftime("%Y-%m-%d %H:%M")except Exception:return dt_strdef format_time(dt_str):"""格式化时间"""try:dt = datetime.fromisoformat(dt_str)return dt.strftime("%H:%M:%S")except Exception:return dt_strdef format_countdown(seconds):"""格式化倒计时"""if seconds < 0:seconds = 0seconds = int(seconds)h = seconds // 3600m = (seconds % 3600) // 60s = seconds % 60if h > 0:return f"{h:02d}:{m:02d}:{s:02d}"return f"{m:02d}:{s:02d}"def get_exam_status(exam):"""获取考试状态"""now = datetime.now()subjects = exam.get("subjects", [])if not subjects:return "waiting"has_ongoing = Falsehas_future = Falseall_ended = Truefor s in subjects:try:start = datetime.fromisoformat(s["start"])end = datetime.fromisoformat(s["end"])except Exception:continueif start <= now < end:has_ongoing = Trueall_ended = Falseif now < start:has_future = Trueall_ended = Falseif has_ongoing:return "ongoing"if has_future:return "waiting"if all_ended and subjects:return "ended"return "waiting"def get_current_subject(exam):"""获取当前科目"""now = datetime.now()subjects = exam.get("subjects", [])for s in subjects:try:start = datetime.fromisoformat(s["start"])end = datetime.fromisoformat(s["end"])if start <= now < end:return sexcept Exception:continue# 找最近的未开始科目nearest = Nonediff = float("inf")for s in subjects:try:start = datetime.fromisoformat(s["start"])if start > now and (start - now).total_seconds() < diff:diff = (start - now).total_seconds()nearest = sexcept Exception:continuereturn nearest==================== 主应用类 ====================class ExamMonitorApp:"""监考大屏系统主应用"""def __init__(self):    self.exams = load_json(DATA_FILE, {"exams": []}).get("exams", [])    self.notifications = load_json(NOTIF_FILE, [])    self.settings = load_json(SETTINGS_FILE, {"theme""cosmo"})    self.current_exam_id = None    self.screen_running = False    self.announced = {}    # 创建主窗口    if HAS_BOOTSTRAP:        theme = self.settings.get("theme""cosmo")        self.root = ttk.Window(            title="📚 监考大屏系统",            themename=theme,            size=(1200750),            minsize=(900600),        )    else:        self.root = tk.Tk()        self.root.title("📚 监考大屏系统")        self.root.geometry("1200x750")        self.root.minsize(900600)    self.root.protocol("WM_DELETE_WINDOW"self.on_close)    # 构建UI    self._build_header()    self._build_notebook()    self._build_list_page()    self._build_form_page()    self._build_screen_page()    # 初始渲染    self.refresh_list()    # 启动定时器    self._tick()def _tick(self):    """全局定时器,每秒更新大屏"""    if self.screen_running:        self._update_screen()    self.root.after(1000self._tick)def on_close(self):    """关闭时保存数据"""    self.save_all()    self.root.destroy()def save_all(self):    """保存所有数据"""    save_json(DATA_FILE, {"exams"self.exams})    save_json(NOTIF_FILE, self.notifications)    save_json(SETTINGS_FILE, self.settings)# ==================== 头部区域 ====================def _build_header(self):    header = ttk.Frame(self.root, padding=10)    header.pack(fill=X)    ttk.Label(        header, text="📚 监考大屏系统", font=(""18"bold")    ).pack(side=LEFT)    # 右侧按钮组    right_frame = ttk.Frame(header)    right_frame.pack(side=RIGHT)    if HAS_BOOTSTRAP:        themes = ["cosmo""darkly""journal""flatly""superhero"]        ttk.Label(right_frame, text="主题:").pack(side=LEFT, padx=(05))        self.theme_var = ttk.StringVar(value=self.settings.get("theme""cosmo"))        theme_cb = ttk.Combobox(            right_frame, textvariable=self.theme_var,            values=themes, width=10, state="readonly"        )        theme_cb.pack(side=LEFT, padx=(010))        theme_cb.bind("<<ComboboxSelected>>"self._change_theme)    ttk.Button(        right_frame, text="+ 新建考试",        command=self._open_create_form,        bootstyle="primary" if HAS_BOOTSTRAP else None    ).pack(side=LEFT, padx=5)def _change_theme(self, event=None):    """切换主题"""    if HAS_BOOTSTRAP:        theme = self.theme_var.get()        self.settings["theme"] = theme        self.root.style.theme_use(theme)        save_json(SETTINGS_FILE, self.settings)# ==================== Notebook 页面 ====================def _build_notebook(self):    self.notebook = ttk.Notebook(self.root)    self.notebook.pack(fill=BOTH, expand=True, padx=10, pady=(010))    self.list_frame = ttk.Frame(self.notebook, padding=10)    self.form_frame = ttk.Frame(self.notebook, padding=10)    self.screen_frame = ttk.Frame(self.notebook, padding=0)    self.notebook.add(self.list_frame, text="📋 考试列表")    self.notebook.add(self.form_frame, text="✏️ 编辑考试")    self.notebook.add(self.screen_frame, text="🖥️ 监控大屏")    self.notebook.bind("<<NotebookTabChanged>>"self._on_tab_change)def _on_tab_change(self, event=None):    idx = self.notebook.index("current")    if idx == 0:        self.refresh_list()        self.screen_running = False    elif idx == 2:        self.screen_running = True        self._update_screen()    else:        self.screen_running = False# ==================== 列表页 ====================def _build_list_page(self):    # 搜索栏    top_bar = ttk.Frame(self.list_frame)    top_bar.pack(fill=X, pady=(010))    ttk.Label(top_bar, text="📋 所有考试", font=(""14"bold")).pack(side=LEFT)    self.search_var = ttk.StringVar()    self.search_var.trace_add("write"lambda *_: self.refresh_list())    search_entry = ttk.Entry(top_bar, textvariable=self.search_var, width=25)    search_entry.pack(side=RIGHT, padx=5)    ttk.Label(top_bar, text="搜索:").pack(side=RIGHT)    # 表格区域    columns = ("name""school""subjects""status""created")    self.tree = ttk.Treeview(        self.list_frame, columns=columns, show="headings", height=12    )    self.tree.heading("name", text="考试名称")    self.tree.heading("school", text="学校")    self.tree.heading("subjects", text="科目数")    self.tree.heading("status", text="状态")    self.tree.heading("created", text="创建时间")    self.tree.column("name", width=200)    self.tree.column("school", width=150)    self.tree.column("subjects", width=80, anchor=CENTER)    self.tree.column("status", width=100, anchor=CENTER)    self.tree.column("created", width=160)    scrollbar = ttk.Scrollbar(self.list_frame, orient=VERTICAL, command=self.tree.yview)    self.tree.configure(yscrollcommand=scrollbar.set)    self.tree.pack(side=LEFT, fill=BOTH, expand=True)    scrollbar.pack(side=RIGHT, fill=Y)    # 操作按钮栏    btn_frame = ttk.Frame(self.list_frame)    btn_frame.pack(fill=X, pady=(100), side=BOTTOM)    ttk.Button(btn_frame, text="🖥️ 进入大屏", command=self._enter_screen,               bootstyle="success" if HAS_BOOTSTRAP else None).pack(side=LEFT, padx=3)    ttk.Button(btn_frame, text="✏️ 编辑", command=self._edit_selected,               bootstyle="info" if HAS_BOOTSTRAP else None).pack(side=LEFT, padx=3)    ttk.Button(btn_frame, text="📢 发通知", command=self._send_notif_dialog,               bootstyle="warning" if HAS_BOOTSTRAP else None).pack(side=LEFT, padx=3)    ttk.Button(btn_frame, text="🗑️ 删除", command=self._delete_selected,               bootstyle="danger" if HAS_BOOTSTRAP else None).pack(side=LEFT, padx=3)    # 通知历史    notif_frame = ttk.LabelFrame(self.list_frame, text="📢 最近通知")    notif_frame.pack(fill=X, pady=(100), side=BOTTOM)    self.notif_listbox = tk.Listbox(notif_frame, height=4, font=(""10)) if not HAS_BOOTSTRAP else ttk.Treeview(        notif_frame, columns=("content""time"), show="headings", height=4    )    if HAS_BOOTSTRAP:        self.notif_listbox.heading("content", text="内容")        self.notif_listbox.heading("time", text="时间")        self.notif_listbox.column("content", width=400)        self.notif_listbox.column("time", width=160)    self.notif_listbox.pack(fill=X)def refresh_list(self):    """刷新考试列表"""    for item in self.tree.get_children():        self.tree.delete(item)    search = self.search_var.get().strip().lower()    status_map = {"waiting""等待开始""ongoing""进行中""ended""已结束"}    for exam in self.exams:        name = exam.get("name""")        if search and search not in name.lower():            continue        school = exam.get("school""")        subj_count = len(exam.get("subjects", []))        status = get_exam_status(exam)        created = format_datetime(exam.get("createdAt"""))        self.tree.insert("", END, iid=exam["id"], values=(            name, school, subj_count, status_map.get(status, ""), created        ))    # 刷新通知列表    self._refresh_notifications()def _refresh_notifications(self):    """刷新通知历史"""    if HAS_BOOTSTRAP:        for item in self.notif_listbox.get_children():            self.notif_listbox.delete(item)        recent = self.notifications[-10:][::-1]        for n in recent:            self.notif_listbox.insert("", END, values=(                n.get("content"""), format_datetime(n.get("sentAt"""))            ))    else:        self.notif_listbox.delete(0, tk.END)        recent = self.notifications[-10:][::-1]        for n in recent:            self.notif_listbox.insert(tk.END,                f"{n.get('content''')}  ({format_datetime(n.get('sentAt'''))})")def _get_selected_exam(self):    """获取选中的考试"""    sel = self.tree.selection()    if not sel:        self._show_msg("提示""请先选择一个考试")        return None    exam_id = sel[0]    for exam in self.exams:        if exam["id"] == exam_id:            return exam    return Nonedef _enter_screen(self):    """进入大屏"""    exam = self._get_selected_exam()    if not exam:        return    self.current_exam_id = exam["id"]    self.announced = {}    self.screen_running = True    self.notebook.select(2)    self._update_screen()def _edit_selected(self):    """编辑选中考试"""    exam = self._get_selected_exam()    if not exam:        return    self._load_form(exam)    self.notebook.select(1)def _delete_selected(self):    """删除选中考试"""    exam = self._get_selected_exam()    if not exam:        return    if HAS_BOOTSTRAP:        result = Messagebox.yesno("确定删除该考试吗?""确认删除")        if result != "Yes":            return    else:        if not messagebox.askyesno("确认删除""确定删除该考试吗?"):            return    self.exams = [e for e in self.exams if e["id"] != exam["id"]]    self.save_all()    self.refresh_list()def _send_notif_dialog(self):    """发送通知对话框"""    exam = self._get_selected_exam()    if not exam:        return    self._open_notification_window(exam["id"])# ==================== 表单页 ====================def _build_form_page(self):    self.form_edit_id = None    # 滚动容器    if HAS_BOOTSTRAP:        sf = ScrolledFrame(self.form_frame, autohide=True)        sf.pack(fill=BOTH, expand=True)        container = sf    else:        canvas = tk.Canvas(self.form_frame)        scrollbar = ttk.Scrollbar(self.form_frame, orient=VERTICAL, command=canvas.yview)        container = ttk.Frame(canvas)        container.bind("<Configure>"lambda e: canvas.configure(scrollregion=canvas.bbox("all")))        canvas.create_window((00), window=container, anchor="nw")        canvas.configure(yscrollcommand=scrollbar.set)        canvas.pack(side=LEFT, fill=BOTH, expand=True)        scrollbar.pack(side=RIGHT, fill=Y)    # 标题    self.form_title_label = ttk.Label(container, text="新建考试", font=(""16"bold"))    self.form_title_label.pack(anchor=W, pady=(015))    # 考试名称    row1 = ttk.Frame(container)    row1.pack(fill=X, pady=5)    ttk.Label(row1, text="考试名称 *", font=(""11)).pack(anchor=W)    self.form_name_var = ttk.StringVar()    ttk.Entry(row1, textvariable=self.form_name_var, font=(""11)).pack(fill=X, pady=2)    # 学校名称    row2 = ttk.Frame(container)    row2.pack(fill=X, pady=5)    ttk.Label(row2, text="学校名称", font=(""11)).pack(anchor=W)    self.form_school_var = ttk.StringVar()    ttk.Entry(row2, textvariable=self.form_school_var, font=(""11)).pack(fill=X, pady=2)    # 科目列表    subj_label_frame = ttk.LabelFrame(container, text="科目时间表")    subj_label_frame.pack(fill=BOTH, expand=True, pady=10)    self.subjects_container = ttk.Frame(subj_label_frame)    self.subjects_container.pack(fill=BOTH, expand=True)    self.subject_widgets = []    ttk.Button(        subj_label_frame, text="+ 添加科目", command=self._add_subject_row,        bootstyle="outline" if HAS_BOOTSTRAP else None    ).pack(anchor=W, pady=(100))    # 按钮    btn_row = ttk.Frame(container)    btn_row.pack(fill=X, pady=15)    ttk.Button(btn_row, text="💾 保存", command=self._save_exam,               bootstyle="success" if HAS_BOOTSTRAP else None).pack(side=RIGHT, padx=5)    ttk.Button(btn_row, text="取消", command=self._cancel_form,               bootstyle="secondary" if HAS_BOOTSTRAP else None).pack(side=RIGHT, padx=5)def _add_subject_row(self, data=None):    """添加一行科目"""    row_frame = ttk.Frame(self.subjects_container, padding=5)    row_frame.pack(fill=X, pady=3)    # 边框效果    if HAS_BOOTSTRAP:        row_frame.configure(bootstyle="default")    name_var = ttk.StringVar(value=data.get("name"""if data else "")    start_var = ttk.StringVar(value=data.get("start"""if data else "")    end_var = ttk.StringVar(value=data.get("end"""if data else "")    audio_var = ttk.StringVar(value=data.get("audioFile"""if data else "")    playtime_var = ttk.StringVar(value=data.get("audioPlayTime"""if data else "")    ttk.Label(row_frame, text="科目:").pack(side=LEFT, padx=2)    ttk.Entry(row_frame, textvariable=name_var, width=10).pack(side=LEFT, padx=2)    ttk.Label(row_frame, text="开始:").pack(side=LEFT, padx=2)    ttk.Entry(row_frame, textvariable=start_var, width=16).pack(side=LEFT, padx=2)    ttk.Label(row_frame, text="结束:").pack(side=LEFT, padx=2)    ttk.Entry(row_frame, textvariable=end_var, width=16).pack(side=LEFT, padx=2)    ttk.Label(row_frame, text="音频播放:").pack(side=LEFT, padx=2)    ttk.Entry(row_frame, textvariable=playtime_var, width=16).pack(side=LEFT, padx=2)    def browse_audio():        path = filedialog.askopenfilename(            filetypes=[("音频文件""*.mp3 *.wav *.ogg *.m4a"), ("所有文件""*.*")]        )        if path:            audio_var.set(path)    ttk.Button(row_frame, text="📁", command=browse_audio, width=3).pack(side=LEFT, padx=2)    def remove_row():        if len(self.subject_widgets) <= 1:            self._show_msg("提示""至少保留一个科目")            return        row_frame.destroy()        self.subject_widgets = [w for w in self.subject_widgets if w["frame"is not row_frame]    ttk.Button(row_frame, text="✕", command=remove_row, width=3,               bootstyle="danger-outline" if HAS_BOOTSTRAP else None).pack(side=LEFT, padx=2)    self.subject_widgets.append({        "frame": row_frame,        "name": name_var,        "start": start_var,        "end": end_var,        "audio": audio_var,        "playtime": playtime_var,    })def _open_create_form(self):    """打开新建表单"""    self.form_edit_id = None    self.form_title_label.config(text="新建考试")    self.form_name_var.set("")    self.form_school_var.set("")    self._clear_subjects()    self._add_subject_row()    self.notebook.select(1)def _load_form(self, exam):    """加载考试到表单"""    self.form_edit_id = exam["id"]    self.form_title_label.config(text="编辑考试")    self.form_name_var.set(exam.get("name"""))    self.form_school_var.set(exam.get("school"""))    self._clear_subjects()    subjects = exam.get("subjects", [])    if subjects:        for s in subjects:            self._add_subject_row(s)    else:        self._add_subject_row()def _clear_subjects(self):    """清空科目列表"""    for w in self.subject_widgets:        w["frame"].destroy()    self.subject_widgets.clear()def _save_exam(self):    """保存考试"""    name = self.form_name_var.get().strip()    if not name:        self._show_msg("错误""请输入考试名称")        return    school = self.form_school_var.get().strip()    subjects = []    for w in self.subject_widgets:        sname = w["name"].get().strip()        sstart = w["start"].get().strip()        send = w["end"].get().strip()        audio = w["audio"].get().strip()        playtime = w["playtime"].get().strip()        if not sname or not sstart or not send:            self._show_msg("错误""请完整填写科目名称、开始和结束时间\n格式: 2026-06-25T08:30")            return        subj = {"name": sname, "start": sstart, "end": send}        if audio:            subj["audioFile"] = audio        if playtime:            subj["audioPlayTime"] = playtime        subjects.append(subj)    if self.form_edit_id:        for exam in self.exams:            if exam["id"] == self.form_edit_id:                exam["name"] = name                exam["school"] = school                exam["subjects"] = subjects                break    else:        self.exams.append({            "id": generate_id(),            "name": name,            "school": school,            "subjects": subjects,            "createdAt": datetime.now().isoformat(),        })    self.save_all()    self._show_msg("成功""考试已保存")    self.notebook.select(0)    self.refresh_list()def _cancel_form(self):    """取消编辑"""    self.notebook.select(0)# ==================== 大屏页 ====================def _build_screen_page(self):    # 深色背景模拟大屏    self.screen_canvas = tk.Canvas(        self.screen_frame, bg="#0b1a33", highlightthickness=0    )    self.screen_canvas.pack(fill=BOTH, expand=True)    # 绑定resize事件    self.screen_canvas.bind("<Configure>"self._on_screen_resize)    # 大屏文本元素    self.scr_exam_title = self.screen_canvas.create_text(        00, text="未选择考试", fill="#ffffff",        font=(""28"bold"), anchor="center"    )    self.scr_school = self.screen_canvas.create_text(        00, text="", fill="#aabbcc",        font=(""14), anchor="center"    )    self.scr_subject = self.screen_canvas.create_text(        00, text="", fill="#ffffff",        font=(""22), anchor="center"    )    self.scr_countdown = self.screen_canvas.create_text(        00, text="--:--", fill="#4f7df3",        font=("Consolas"52"bold"), anchor="center"    )    self.scr_time_range = self.screen_canvas.create_text(        00, text="", fill="#8899aa",        font=(""13), anchor="center"    )    self.scr_current_time = self.screen_canvas.create_text(        00, text="", fill="#667788",        font=(""12), anchor="center"    )    self.scr_status = self.screen_canvas.create_text(        00, text="请从列表选择考试进入大屏", fill="#ffcc00",        font=(""16), anchor="center"    )    # 圆形进度条参数    self.progress_arc = None    self.progress_bg = Nonedef _on_screen_resize(self, event=None):    """大屏区域resize时重新布局"""    w = self.screen_canvas.winfo_width()    h = self.screen_canvas.winfo_height()    cx, cy = w // 2, h // 2    self.screen_canvas.coords(self.scr_exam_title, cx, cy - 180)    self.screen_canvas.coords(self.scr_school, cx, cy - 145)    self.screen_canvas.coords(self.scr_subject, cx, cy - 100)    self.screen_canvas.coords(self.scr_countdown, cx, cy)    self.screen_canvas.coords(self.scr_time_range, cx, cy + 70)    self.screen_canvas.coords(self.scr_current_time, cx, cy + 100)    self.screen_canvas.coords(self.scr_status, cx, cy + 150)    # 重绘进度环    self._draw_progress_ring(cx, cy, 0)def _draw_progress_ring(self, cx, cy, progress):    """绘制圆形进度环"""    r = 80    # 删除旧的    if self.progress_bg:        self.screen_canvas.delete(self.progress_bg)    if self.progress_arc:        self.screen_canvas.delete(self.progress_arc)    # 背景环    self.progress_bg = self.screen_canvas.create_oval(        cx - r, cy - r, cx + r, cy + r,        outline="#1a3355", width=8    )    # 进度弧    extent = -360 * progress    self.progress_arc = self.screen_canvas.create_arc(        cx - r, cy - r, cx + r, cy + r,        start=90, extent=extent,        outline="#4f7df3", width=8, style="arc"    )    # 确保文本在最上层    self.screen_canvas.tag_raise(self.scr_countdown)def _update_screen(self):    """更新大屏显示"""    exam = None    for e in self.exams:        if e["id"] == self.current_exam_id:            exam = e            break    if not exam:        self.screen_canvas.itemconfig(self.scr_exam_title, text="未选择考试")        self.screen_canvas.itemconfig(self.scr_subject, text="")        self.screen_canvas.itemconfig(self.scr_countdown, text="--:--")        self.screen_canvas.itemconfig(self.scr_status, text="请从列表选择考试进入大屏")        return    self.screen_canvas.itemconfig(self.scr_exam_title, text=exam.get("name"""))    self.screen_canvas.itemconfig(self.scr_school, text=exam.get("school"""))    now = datetime.now()    current_time_str = now.strftime("%H:%M:%S")    self.screen_canvas.itemconfig(self.scr_current_time, text=f"当前时间: {current_time_str}")    subj = get_current_subject(exam)    w = self.screen_canvas.winfo_width()    h = self.screen_canvas.winfo_height()    cx, cy = w // 2, h // 2    if subj:        try:            start = datetime.fromisoformat(subj["start"])            end = datetime.fromisoformat(subj["end"])        except Exception:            return        self.screen_canvas.itemconfig(self.scr_subject, text=subj["name"])        time_range = f"{start.strftime('%H:%M')} - {end.strftime('%H:%M')}"        self.screen_canvas.itemconfig(self.scr_time_range, text=time_range)        if start <= now < end:            # 考试进行中            remaining = (end - now).total_seconds()            total = (end - start).total_seconds()            progress = remaining / total if total > 0 else 0            self.screen_canvas.itemconfig(self.scr_countdown, text=format_countdown(remaining))            self.screen_canvas.itemconfig(self.scr_status, text="📝 考试进行中")            self._draw_progress_ring(cx, cy, progress)            # 语音提醒(简化:打印到控制台)            key = f"{exam['id']}_{subj['name']}"            if key not in self.announced:                self.announced[key] = {}            if not self.announced[key].get("start"and remaining > total - 5:                self.announced[key]["start"] = True                print("[语音] 考试开始")            if not self.announced[key].get("fifteen"):                if remaining <= 900 and remaining > 895:                    self.announced[key]["fifteen"] = True                    print("[语音] 距离考试结束还有15分钟")        elif now < start:            # 等待开始            remaining = (start - now).total_seconds()            self.screen_canvas.itemconfig(self.scr_countdown, text=format_countdown(remaining))            self.screen_canvas.itemconfig(                self.scr_status, text=f"⏳ 等待开始 ({start.strftime('%H:%M')})"            )            self._draw_progress_ring(cx, cy, 0)        else:            # 已结束            self.screen_canvas.itemconfig(self.scr_countdown, text="00:00")            self.screen_canvas.itemconfig(self.scr_status, text="✅ 本场考试已结束")            self._draw_progress_ring(cx, cy, 0)    else:        # 没有进行中的科目,查找下一场        nearest = None        diff = float("inf")        for s in exam.get("subjects", []):            try:                st = datetime.fromisoformat(s["start"])                if st > now and (st - now).total_seconds() < diff:                    diff = (st - now).total_seconds()                    nearest = s            except Exception:                continue        if nearest:            st = datetime.fromisoformat(nearest["start"])            remaining = (st - now).total_seconds()            self.screen_canvas.itemconfig(self.scr_subject, text=nearest["name"])            self.screen_canvas.itemconfig(self.scr_countdown, text=format_countdown(remaining))            self.screen_canvas.itemconfig(                self.scr_status, text=f"⏳ 下一场: {nearest['name']}{st.strftime('%H:%M')}"            )            self._draw_progress_ring(cx, cy, 0)        else:            self.screen_canvas.itemconfig(self.scr_subject, text="--")            self.screen_canvas.itemconfig(self.scr_countdown, text="00:00")            self.screen_canvas.itemconfig(self.scr_status, text="🏁 全部考试已结束")            self._draw_progress_ring(cx, cy, 0)# ==================== 通知系统 ====================def _open_notification_window(self, exam_id):    """打开通知发送窗口"""    win = tk.Toplevel(self.root)    win.title("📢 发送通知")    win.geometry("400x280")    win.resizable(FalseFalse)    win.transient(self.root)    win.grab_set()    ttk.Label(win, text="📢 发送通知", font=(""14"bold")).pack(pady=(1510))    ttk.Label(win, text="通知内容 *").pack(anchor=W, padx=20)    content_text = tk.Text(win, height=4, font=(""11))    content_text.pack(fill=X, padx=20, pady=5)    dur_frame = ttk.Frame(win)    dur_frame.pack(fill=X, padx=20, pady=5)    ttk.Label(dur_frame, text="显示时长(秒, 0=一直显示):").pack(side=LEFT)    dur_var = ttk.StringVar(value="15")    ttk.Entry(dur_frame, textvariable=dur_var, width=8).pack(side=LEFT, padx=5)    def send():        content = content_text.get("1.0", tk.END).strip()        if not content:            self._show_msg("错误""请输入通知内容")            return        try:            duration = int(dur_var.get())        except ValueError:            duration = 15        notif = {            "id": generate_id(),            "examId": exam_id,            "content": content,            "sentAt": datetime.now().isoformat(),            "duration": duration,        }        self.notifications.append(notif)        self.save_all()        self._refresh_notifications()        # 如果当前大屏显示的就是这个考试,弹出通知        if self.current_exam_id == exam_id and self.screen_running:            self._show_screen_notification(notif)        win.destroy()        self._show_msg("成功""通知已发送!")    btn_frame = ttk.Frame(win)    btn_frame.pack(pady=15)    ttk.Button(btn_frame, text="发送", command=send,               bootstyle="primary" if HAS_BOOTSTRAP else None).pack(side=LEFT, padx=5)    ttk.Button(btn_frame, text="取消", command=win.destroy,               bootstyle="secondary" if HAS_BOOTSTRAP else None).pack(side=LEFT, padx=5)def _show_screen_notification(self, notif):    """在大屏上显示通知弹窗"""    win = tk.Toplevel(self.root)    win.title("📢 通知")    win.geometry("500x250")    win.resizable(FalseFalse)    win.configure(bg="#1a2b4c")    win.attributes("-topmost"True)    ttk.Label(win, text="📢 通知", font=(""20"bold"),              foreground="#e53e3e", background="#1a2b4c").pack(pady=(2010))    ttk.Label(win, text=notif["content"], font=(""16),              foreground="#ffffff", background="#1a2b4c",              wraplength=450).pack(pady=10)    ttk.Label(win, text=f"发送于 {format_datetime(notif['sentAt'])}",              font=(""10), foreground="#8899aa",              background="#1a2b4c").pack(pady=5)    ttk.Button(win, text="关闭", command=win.destroy).pack(pady=10)    # 自动关闭    duration = notif.get("duration"15)    if duration > 0:        win.after(duration * 1000lambda: win.destroy() if win.winfo_exists() else None)# ==================== 工具方法 ====================def _show_msg(self, title, message):    """显示消息"""    if HAS_BOOTSTRAP:        Messagebox.ok(message, title)    else:        messagebox.showinfo(title, message)def run(self):    """启动应用"""    self.root.mainloop()==================== 入口 ====================if name == "main":app = ExamMonitorApp()app.run()

最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-07-03 00:57:02 HTTP/2.0 GET : https://f.mffb.com.cn/a/501763.html
  2. 运行时间 : 0.271248s [ 吞吐率:3.69req/s ] 内存消耗:4,531.95kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=25d5ca3135b9989c25a8220b0832b31c
  1. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_static.php ( 4.90 KB )
  7. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  10. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  11. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  12. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  13. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  14. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  15. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  16. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  17. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  18. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  19. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  21. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  22. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/provider.php ( 0.19 KB )
  23. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  24. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  25. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  26. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/common.php ( 0.03 KB )
  27. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  28. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  29. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/app.php ( 0.95 KB )
  30. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cache.php ( 0.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/console.php ( 0.23 KB )
  32. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cookie.php ( 0.56 KB )
  33. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/database.php ( 2.48 KB )
  34. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  35. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/filesystem.php ( 0.61 KB )
  36. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/lang.php ( 0.91 KB )
  37. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/log.php ( 1.35 KB )
  38. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/middleware.php ( 0.19 KB )
  39. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/route.php ( 1.89 KB )
  40. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/session.php ( 0.57 KB )
  41. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/trace.php ( 0.34 KB )
  42. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/view.php ( 0.82 KB )
  43. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/event.php ( 0.25 KB )
  44. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  45. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/service.php ( 0.13 KB )
  46. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/AppService.php ( 0.26 KB )
  47. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  48. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  49. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  50. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  51. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  52. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/services.php ( 0.14 KB )
  53. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  54. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  55. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  56. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  57. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  58. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  59. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  60. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  61. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  62. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  63. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  64. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  65. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  66. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  67. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  68. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  69. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  70. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  71. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  72. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  73. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  74. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  75. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  76. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  77. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  78. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  79. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  80. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  81. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  82. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  83. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/Request.php ( 0.09 KB )
  84. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  85. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/middleware.php ( 0.25 KB )
  86. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  87. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  88. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  89. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  90. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  91. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  92. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  93. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  94. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  95. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  96. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  97. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  98. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  99. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/route/app.php ( 1.72 KB )
  100. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  101. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  102. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  103. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/controller/Index.php ( 4.81 KB )
  104. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/BaseController.php ( 2.05 KB )
  105. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  106. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  108. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  109. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  110. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  111. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  112. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  113. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  114. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  115. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  116. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  117. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  118. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  119. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  120. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  121. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  122. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  123. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  124. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  125. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  126. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  127. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  128. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  129. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  130. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  131. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  132. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  133. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  134. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  135. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  136. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  137. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  138. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  139. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/runtime/temp/067d451b9a0c665040f3f1bdd3293d68.php ( 11.98 KB )
  140. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.000751s ] mysql:host=127.0.0.1;port=3306;dbname=f_mffb;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.001064s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.004198s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.019202s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.001168s ]
  6. SELECT * FROM `set` [ RunTime:0.007524s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.001155s ]
  8. SELECT * FROM `article` WHERE `id` = 501763 LIMIT 1 [ RunTime:0.037720s ]
  9. UPDATE `article` SET `lasttime` = 1783011422 WHERE `id` = 501763 [ RunTime:0.004798s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 66 LIMIT 1 [ RunTime:0.000377s ]
  11. SELECT * FROM `article` WHERE `id` < 501763 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.012389s ]
  12. SELECT * FROM `article` WHERE `id` > 501763 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.020147s ]
  13. SELECT * FROM `article` WHERE `id` < 501763 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.004962s ]
  14. SELECT * FROM `article` WHERE `id` < 501763 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.032757s ]
  15. SELECT * FROM `article` WHERE `id` < 501763 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.006952s ]
0.272788s