当前位置:首页>python>Python Tkinter工控实战:IO点位映射显示,从零搭建你的数字沙盘

Python Tkinter工控实战:IO点位映射显示,从零搭建你的数字沙盘

  • 2026-03-01 02:10:28
Python Tkinter工控实战:IO点位映射显示,从零搭建你的数字沙盘

🎬 开场:那些年被IO点位搞崩的深夜

做过工控项目的朋友都懂——凌晨两点,车间里几十路IO信号乱跑,你盯着一堆0和1傻眼,完全不知道哪个点位对应哪台设备、哪个传感器。

这不是段子。这是我头两年做PLC上位机时的真实写照。

当时最大的问题不是"信号读不到",而是**"读到了但不知道这是什么"**。工程图纸厚厚一叠,IO地址表密密麻麻,翻来翻去还容易翻错页。更别提现场调试时,甲方工程师站在旁边催你,你手忙脚乱地查表对地址……那滋味,真不好受。

后来我琢磨了一个方向:用Tkinter做一个可视化的IO点位映射面板,把所有点位、状态、描述信息一屏显示,实时刷新,还能按区域分组。投入不大,但现场调试效率直接翻倍。

今天这篇文章,就把这套东西从头讲清楚。不绕弯子,直接上干货。


🔍 问题剖析:IO管理到底难在哪?

痛点一:点位多、分散、缺语义

一个中等规模的自动化项目,IO点位少则几十、多则几百。DI(数字输入)、DO(数字输出)、AI(模拟输入)、AO(模拟输出)混在一起,地址命名还各家厂商各有套路。

工程师看地址Q0.0%MW100,脑子里得先过一遍"这是什么",这个翻译过程才是效率杀手

痛点二:状态变化快,人眼追不上

IO信号变化是毫秒级的。用print打日志?刷屏看不过来。用Excel记录?事后才能分析。真正需要的是实时的、有颜色区分的状态可视化——亮绿就是1,暗灰就是0,一眼扫过去全知道。

痛点三:上位机界面和IO逻辑强耦合

很多人第一次写上位机,把IO读取逻辑、界面刷新逻辑、数据处理全塞一个函数里。项目小还好,一旦点位增加,改一处就崩一片。这是设计问题,不是技术问题。


💡 核心设计理念

在动手写代码之前,先把架构想清楚,后面会省很多事。

咱们这套IO映射面板的设计核心,就三个字:分、映、刷

  • • :把IO点位按功能区域分组(进料区、加工区、出料区等)
  • • :建立地址→描述的映射表,让机器地址变成人话
  • • :后台线程周期性刷新状态,主线程只管渲染

这三件事分清楚,代码自然就清爽了。


🛠️ 方案一:基础款——静态IO点位映射面板

先从最简单的开始。把IO点位信息硬编码进去,用Canvas或Frame画出状态指示灯,跑通整个流程。

import tkinter as tkfrom tkinter import ttkimport randomimport threadingimport time# ─────────────────────────────────────────# IO点位配置表:地址 → (描述, 区域, 类型)# 实际项目里这张表从数据库或配置文件读# ─────────────────────────────────────────IO_MAP = {"DI_0001": ("进料传感器A",  "进料区""DI"),"DI_0002": ("进料传感器B",  "进料区""DI"),"DI_0003": ("安全门状态",   "进料区""DI"),"DO_0001": ("进料电机启动""进料区""DO"),"DO_0002": ("警示灯红",     "进料区""DO"),"DI_0010": ("夹具到位",     "加工区""DI"),"DI_0011": ("刀具原点",     "加工区""DI"),"DO_0010": ("主轴启动",     "加工区""DO"),"DO_0011": ("冷却泵",       "加工区""DO"),"DI_0020": ("出料到位",     "出料区""DI"),"DO_0020": ("推料气缸",     "出料区""DO"),"DO_0021": ("传送带正转",   "出料区""DO"),}# 模拟IO状态(真实项目里从PLC读取)io_state = {addr: 0for addr in IO_MAP}classIOPointWidget(tk.Frame):"""单个IO点位的可视化组件"""# 颜色配置:类型 × 状态    COLOR_MAP = {"DI": {1"#2ECC71"0"#1A3A2A"},   # 绿色系"DO": {1"#E74C3C"0"#3A1A1A"},   # 红色系    }def__init__(self, parent, addr, desc, io_type, **kwargs):super().__init__(parent, bg="#1E1E2E", **kwargs)self.addr = addrself.io_type = io_typeself._state = 0# 状态指示灯(Canvas圆形)self.canvas = tk.Canvas(self, width=18, height=18,                                bg="#1E1E2E", highlightthickness=0)self.canvas.pack(side=tk.LEFT, padx=(46), pady=4)self.led = self.canvas.create_oval(221616,                                           fill=self.COLOR_MAP[io_type][0],                                           outline="#333")# 地址标签        tk.Label(self, text=addr, font=("Consolas"9),                 fg="#7F8C8D", bg="#1E1E2E", width=10,                 anchor="w").pack(side=tk.LEFT)# 描述标签        tk.Label(self, text=desc, font=("微软雅黑"9),                 fg="#ECF0F1", bg="#1E1E2E", width=12,                 anchor="w").pack(side=tk.LEFT)# 类型徽章        badge_color = "#27AE60"if io_type == "DI"else"#C0392B"        tk.Label(self, text=io_type, font=("Consolas"8"bold"),                 fg="white", bg=badge_color,                 padx=4).pack(side=tk.LEFT, padx=4)defset_state(self, state: int):"""更新点位状态(0/1)"""if state == self._state:return# 没变化就跳过,减少重绘self._state = state        color = self.COLOR_MAP[self.io_type][state]self.canvas.itemconfig(self.led, fill=color)classIOPanelApp(tk.Tk):def__init__(self):super().__init__()self.title("IO点位映射监控面板 v1.0")self.configure(bg="#12121F")self.geometry("780x620")self.resizable(TrueTrue)self._widgets: dict[str, IOPointWidget] = {}self._build_ui()self._start_refresh_thread()def_build_ui(self):# 顶部标题栏        header = tk.Frame(self, bg="#0D0D1A", height=50)        header.pack(fill=tk.X)        tk.Label(header, text="⚡ IO点位实时监控",                 font=("微软雅黑"14"bold"),                 fg="#F39C12", bg="#0D0D1A").pack(side=tk.LEFT, padx=16, pady=10)self.status_label = tk.Label(header, text="● 运行中",                                     font=("微软雅黑"10),                                     fg="#2ECC71", bg="#0D0D1A")self.status_label.pack(side=tk.RIGHT, padx=16)# 主体滚动区域        container = tk.Frame(self, bg="#12121F")        container.pack(fill=tk.BOTH, expand=True, padx=10, pady=8)        canvas = tk.Canvas(container, bg="#12121F", highlightthickness=0)        scrollbar = ttk.Scrollbar(container, orient="vertical",                                  command=canvas.yview)self.scroll_frame = tk.Frame(canvas, bg="#12121F")self.scroll_frame.bind("<Configure>",lambda e: canvas.configure(scrollregion=canvas.bbox("all")))        canvas.create_window((00), window=self.scroll_frame, anchor="nw")        canvas.configure(yscrollcommand=scrollbar.set)        canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)# 按区域分组渲染        groups: dict[strlist] = {}for addr, (desc, zone, iotype) in IO_MAP.items():            groups.setdefault(zone, []).append((addr, desc, iotype))for zone, points in groups.items():self._render_group(zone, points)# 底部统计栏self._build_status_bar()def_render_group(self, zone: str, points: list):"""渲染一个区域分组"""# 区域标题        zone_frame = tk.Frame(self.scroll_frame, bg="#1A1A2E",                              relief=tk.FLAT, bd=0)        zone_frame.pack(fill=tk.X, padx=6, pady=(102))        tk.Label(zone_frame, text=f"📦 {zone}",                 font=("微软雅黑"11"bold"),                 fg="#3498DB", bg="#1A1A2E",                 pady=6, padx=10).pack(side=tk.LEFT)        tk.Label(zone_frame, text=f"{len(points)} 个点位",                 font=("微软雅黑"9),                 fg="#7F8C8D", bg="#1A1A2E").pack(side=tk.RIGHT, padx=10)# 分割线        sep = tk.Frame(self.scroll_frame, bg="#2C3E50", height=1)        sep.pack(fill=tk.X, padx=6)# 点位列表for addr, desc, iotype in points:            widget = IOPointWidget(self.scroll_frame, addr, desc, iotype)            widget.pack(fill=tk.X, padx=12, pady=1)self._widgets[addr] = widgetdef_build_status_bar(self):        bar = tk.Frame(self, bg="#0D0D1A", height=30)        bar.pack(fill=tk.X, side=tk.BOTTOM)self.stat_label = tk.Label(bar, text="",                                   font=("Consolas"9),                                   fg="#95A5A6", bg="#0D0D1A")self.stat_label.pack(side=tk.LEFT, padx=12)def_start_refresh_thread(self):"""后台线程:模拟IO状态变化"""defworker():whileTrue:# 真实项目里,这里换成读PLC/Modbus/OPC-UA的代码for addr in io_state:if random.random() < 0.08:  # 8%概率翻转                        io_state[addr] ^= 1# 通过after()回到主线程更新界面——这是关键!self.after(0self._refresh_ui)                time.sleep(0.5)        t = threading.Thread(target=worker, daemon=True)        t.start()def_refresh_ui(self):"""主线程刷新所有点位状态"""        on_count = 0for addr, widget inself._widgets.items():            state = io_state.get(addr, 0)            widget.set_state(state)            on_count += state        total = len(self._widgets)self.stat_label.config(            text=f"总点位: {total}  |  激活: {on_count}  |  "f"未激活: {total - on_count}  |  "f"刷新: {time.strftime('%H:%M:%S')}"        )if __name__ == "__main__":    app = IOPanelApp()    app.mainloop()

⚠️ 踩坑预警绝对不要在后台线程里直接操作Tkinter控件!Tkinter不是线程安全的,这么干迟早崩。正确姿势是后台线程计算好数据,用self.after(0, callback)派发到主线程执行。这条规则记死了。


🚀 方案二:进阶款——配置文件驱动 + 搜索过滤

硬编码的IO_MAP在小项目还凑合,一旦点位上百,就得换个活法。这一版把点位配置写进JSON,还加了搜索框,现场查点位贼方便。

import json  import tkinter as tk  from tkinter import ttk  import random  import threading  import time  import os  defcreate_sample_config():  """创建示例配置文件"""    sample_config = {  "DI_0001": {"desc""进料传感器A""zone""进料区""type""DI"},  "DI_0002": {"desc""进料传感器B""zone""进料区""type""DI"},  "DI_0003": {"desc""安全门状态""zone""进料区""type""DI"},  "DO_0001": {"desc""进料电机启动""zone""进料区""type""DO"},  "DO_0002": {"desc""警示灯红""zone""进料区""type""DO"},  "DI_0010": {"desc""夹具到位""zone""加工区""type""DI"},  "DI_0011": {"desc""刀具原点""zone""加工区""type""DI"},  "DI_0012": {"desc""工件检测""zone""加工区""type""DI"},  "DO_0010": {"desc""主轴启动""zone""加工区""type""DO"},  "DO_0011": {"desc""冷却泵""zone""加工区""type""DO"},  "DO_0012": {"desc""夹紧气缸""zone""加工区""type""DO"},  "DI_0020": {"desc""出料到位""zone""出料区""type""DI"},  "DI_0021": {"desc""堆垛满载""zone""出料区""type""DI"},  "DO_0020": {"desc""推料气缸""zone""出料区""type""DO"},  "DO_0021": {"desc""传送带正转""zone""出料区""type""DO"},  "DO_0022": {"desc""分拣机构""zone""出料区""type""DO"},  "DI_0030": {"desc""急停按钮""zone""安全区""type""DI"},  "DI_0031": {"desc""光栅保护""zone""安全区""type""DI"},  "DO_0030": {"desc""报警蜂鸣器""zone""安全区""type""DO"},  "DO_0031": {"desc""状态指示灯""zone""安全区""type""DO"},      }  withopen("io_config.json""w", encoding="utf-8"as f:          json.dump(sample_config, f, ensure_ascii=False, indent=2)  defload_io_config(path: str) -> dict:  """从JSON加载IO配置,容错处理"""try:  withopen(path, "r", encoding="utf-8"as f:  return json.load(f)  except FileNotFoundError:  print(f"[警告] 配置文件不存在: {path},创建示例配置")          create_sample_config()  return load_io_config(path)  except json.JSONDecodeError as e:  print(f"[错误] JSON格式有问题: {e}")  return {}  classIOPointWidget(tk.Frame):  """单个IO点位的可视化组件"""    COLOR_MAP = {  "DI": {1"#2ECC71"0"#1A3A2A"},  "DO": {1"#E74C3C"0"#3A1A1A"},      }  def__init__(self, parent, addr, desc, io_type, zone, **kwargs):  super().__init__(parent, bg="#1E1E2E", relief=tk.FLAT, bd=1, **kwargs)  self.addr = addr  self.desc = desc  self.io_type = io_type  self.zone = zone  self._state = 0self.bind("<Enter>"lambda e: self.config(bg="#252540"))  self.bind("<Leave>"lambda e: self.config(bg="#1E1E2E"))  self._build_widget()  def_build_widget(self):  # 状态指示灯  self.canvas = tk.Canvas(self, width=16, height=16,                                  bg="#1E1E2E", highlightthickness=0)  self.canvas.pack(side=tk.LEFT, padx=(810), pady=6)  self.led = self.canvas.create_oval(221414,                                             fill=self.COLOR_MAP[self.io_type][0],                                             outline="#444")  # 地址标签          addr_label = tk.Label(self, text=self.addr, font=("Consolas"9"bold"),                                fg="#7F8C8D", bg="#1E1E2E", width=10, anchor="w")          addr_label.pack(side=tk.LEFT, padx=(08))  # 描述标签          desc_label = tk.Label(self, text=self.desc, font=("微软雅黑"9),                                fg="#ECF0F1", bg="#1E1E2E", width=14, anchor="w")          desc_label.pack(side=tk.LEFT, padx=(08))  # 类型徽章          badge_color = "#27AE60"ifself.io_type == "DI"else"#C0392B"        type_label = tk.Label(self, text=self.io_type, font=("Consolas"8"bold"),                                fg="white", bg=badge_color, padx=6, pady=1)          type_label.pack(side=tk.LEFT, padx=(08))  # 区域标签          zone_label = tk.Label(self, text=self.zone, font=("微软雅黑"8),                                fg="#BDC3C7", bg="#34495E", padx=6, pady=1)          zone_label.pack(side=tk.LEFT)  # 状态文本  self.state_label = tk.Label(self, text="OFF", font=("Consolas"8"bold"),                                      fg="#7F8C8D", bg="#1E1E2E", width=4)  self.state_label.pack(side=tk.RIGHT, padx=8)  # 鼠标事件绑定  for widget in [self.canvas, addr_label, desc_label, type_label, zone_label, self.state_label]:              widget.bind("<Enter>"lambda e: self.config(bg="#252540"))              widget.bind("<Leave>"lambda e: self.config(bg="#1E1E2E"))  defset_state(self, state: int):  """更新点位状态"""if state == self._state:  returnself._state = state          color = self.COLOR_MAP[self.io_type][state]  self.canvas.itemconfig(self.led, fill=color)          text = "ON "if state else"OFF"        color = "#2ECC71"if state else"#7F8C8D"self.state_label.config(text=text, fg=color)  classZoneSection:  """区域分组容器,管理区域标题和点位"""def__init__(self, parent, zone_name: str):  self.zone_name = zone_name  self.parent = parent  self.widgets = {}  # 本区域的IO点位控件  # 创建区域框架  self.zone_frame = tk.Frame(parent, bg="#1A1A2E", relief=tk.FLAT, bd=0)  # 区域标题  self.title_label = tk.Label(self.zone_frame, text=f"📦 {zone_name}",                                      font=("微软雅黑"11"bold"),                                      fg="#3498DB", bg="#1A1A2E", pady=6, padx=12)  self.title_label.pack(side=tk.LEFT)  # 点位计数  self.count_label = tk.Label(self.zone_frame, text="0 个点位",                                      font=("微软雅黑"9), fg="#7F8C8D", bg="#1A1A2E")  self.count_label.pack(side=tk.RIGHT, padx=12)  # 分割线  self.separator = tk.Frame(parent, bg="#2C3E50", height=1)  self.is_visible = Falsedefadd_widget(self, addr: str, widget: IOPointWidget):  """添加IO点位控件到本区域"""self.widgets[addr] = widget  self._update_count()  def_update_count(self):  """更新点位计数显示"""        count = len(self.widgets)  self.count_label.config(text=f"{count} 个点位")  defshow(self):  """显示区域(标题和分割线)"""ifnotself.is_visible:  self.zone_frame.pack(fill=tk.X, padx=4, pady=(82))  self.separator.pack(fill=tk.X, padx=4)  self.is_visible = Truedefhide(self):  """隐藏区域"""ifself.is_visible:  self.zone_frame.pack_forget()  self.separator.pack_forget()  self.is_visible = Falsedeffilter_widgets(self, keyword: str) -> int:  """根据关键词过滤本区域的控件,返回可见数量"""        visible_count = 0        has_visible = Falsefor addr, widget inself.widgets.items():  # 判断是否匹配搜索条件              desc = widget.desc.lower()              zone = widget.zone.lower()              visible = (keyword == ""or                       keyword in addr.lower() or                       keyword in desc or                       keyword in zone)  if visible:                  widget.pack(fill=tk.X, padx=8, pady=1)                  visible_count += 1                has_visible = Trueelse:                  widget.pack_forget()  # 根据是否有可见控件决定显示/隐藏区域标题  if has_visible:  self.show()  else:  self.hide()  return visible_count  classSearchableIOPanel(tk.Frame):  """带搜索过滤的IO面板组件(修复版)"""def__init__(self, parent, io_config: dict, **kwargs):  super().__init__(parent, bg="#12121F", **kwargs)  self.io_config = io_config  self.widgets = {}  # 所有IO控件 {addr: widget}        self.zones = {}  # 区域管理器 {zone_name: ZoneSection}        self._search_var = tk.StringVar()  self.stats_label = Noneself.search_entry = Noneself._build()  self._create_widgets()  # 设置搜索监听  self._search_var.trace_add("write"self._on_search)  def_build(self):  # 搜索栏          search_container = tk.Frame(self, bg="#0D0D1A", pady=8)          search_container.pack(fill=tk.X, padx=8, pady=8)          search_frame = tk.Frame(search_container, bg="#1E1E2E", relief=tk.FLAT, bd=1)          search_frame.pack(fill=tk.X)          tk.Label(search_frame, text="🔍", bg="#1E1E2E", fg="#7F8C8D",                   font=(""14)).pack(side=tk.LEFT, padx=(84))  self.search_entry = tk.Entry(search_frame, textvariable=self._search_var,                                       font=("微软雅黑"10), bg="#2C3E50",                                       fg="white", insertbackground="white",                                       relief=tk.FLAT, bd=0)  self.search_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(48), pady=6)  # 占位符  self.placeholder = "输入地址或描述搜索..."self.search_entry.insert(0self.placeholder)  self.search_entry.bind("<FocusIn>"self._on_entry_focus_in)  self.search_entry.bind("<FocusOut>"self._on_entry_focus_out)  # 清空按钮          clear_btn = tk.Label(search_frame, text="✕", bg="#1E1E2E", fg="#7F8C8D",                               font=(""12), cursor="hand2")          clear_btn.pack(side=tk.RIGHT, padx=8)          clear_btn.bind("<Button-1>"lambda e: self._clear_search())  # 统计标签  self.stats_label = tk.Label(search_container, text="正在加载...",                                      font=("微软雅黑"9), fg="#7F8C8D", bg="#0D0D1A")  self.stats_label.pack(pady=(40))  # 滚动区域  self._create_scroll_area()  def_create_scroll_area(self):  """创建滚动区域"""        container = tk.Frame(self, bg="#12121F")          container.pack(fill=tk.BOTH, expand=True, padx=8)  self.canvas = tk.Canvas(container, bg="#12121F", highlightthickness=0)          scrollbar = ttk.Scrollbar(container, orient="vertical", command=self.canvas.yview)  self.scroll_frame = tk.Frame(self.canvas, bg="#12121F")  self.scroll_frame.bind("<Configure>",  lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all")))  self.canvas.create_window((00), window=self.scroll_frame, anchor="nw")  self.canvas.configure(yscrollcommand=scrollbar.set)  self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)          scrollbar.pack(side=tk.RIGHT, fill=tk.Y)  # 鼠标滚轮  self.canvas.bind("<MouseWheel>"self._on_mousewheel)  def_on_mousewheel(self, event):  self.canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")  def_create_widgets(self):  """创建所有IO点位控件"""ifnotself.io_config:              empty_label = tk.Label(self.scroll_frame, text="未找到IO配置信息",                                     font=("微软雅黑"12), fg="#7F8C8D", bg="#12121F")              empty_label.pack(expand=True, pady=50)  return# 按区域分组数据          zone_data = {}  for addr, config inself.io_config.items():              zone = config.get("zone""未分组")  if zone notin zone_data:                  zone_data[zone] = []              zone_data[zone].append((addr, config))  # 创建区域管理器和控件  for zone_name, items in zone_data.items():  # 创建区域管理器              zone_section = ZoneSection(self.scroll_frame, zone_name)  self.zones[zone_name] = zone_section  # 创建本区域的IO控件  for addr, config in items:                  widget = IOPointWidget(  self.scroll_frame, addr,                      config["desc"], config["type"], config["zone"]                  )  # 添加到区域管理器                  zone_section.add_widget(addr, widget)  # 添加到全局控件字典  self.widgets[addr] = widget  # 默认显示                  widget.pack(fill=tk.X, padx=8, pady=1)  # 显示区域标题              zone_section.show()  self._update_stats()  def_on_entry_focus_in(self, event):  ifself.search_entry.get() == self.placeholder:  self.search_entry.delete(0, tk.END)  self.search_entry.config(fg="white")  def_on_entry_focus_out(self, event):  ifnotself.search_entry.get():  self.search_entry.insert(0self.placeholder)  self.search_entry.config(fg="#7F8C8D")  def_clear_search(self):  self._search_var.set("")  ifself.search_entry:  self.search_entry.config(fg="white")  self.search_entry.focus_set()  def_on_search(self, *args):  """🔧 修复后的搜索逻辑 - 保持区域分组"""ifnotself.stats_label ornotself.widgets:  return        keyword = self._search_var.get().strip().lower()  if keyword == self.placeholder.lower():              keyword = ""        total_visible = 0# 按区域过滤,每个区域单独处理  for zone_name, zone_section inself.zones.items():              visible_count = zone_section.filter_widgets(keyword)              total_visible += visible_count  self._update_stats(total_visible, len(self.widgets))  def_update_stats(self, visible=None, total=None):  ifnotself.stats_label:  returnif visible isNone:              visible = len(self.widgets)  if total isNone:              total = len(self.widgets)  if visible == total:              text = f"共 {total} 个IO点位"else:              text = f"显示 {visible}/{total} 个IO点位"self.stats_label.config(text=text)  defupdate_states(self, states: dict):  """批量更新IO状态"""for addr, state in states.items():  if addr inself.widgets:  self.widgets[addr].set_state(state)  classIOMonitorApp(tk.Tk):  """IO监控主应用"""def__init__(self):  super().__init__()  self.title("IO点位搜索监控面板 v2.1")  self.configure(bg="#0A0A0A")  self.geometry("900x700")  self.resizable(TrueTrue)  self.io_config = load_io_config("io_config.json")  self.io_states = {addr: 0for addr inself.io_config}  self._build_ui()  self._start_simulation()  def_build_ui(self):  # 标题栏          header = tk.Frame(self, bg="#0D0D1A", height=50)          header.pack(fill=tk.X)          header.pack_propagate(False)          title_label = tk.Label(header, text="⚡ IO点位实时监控系统",                                 font=("微软雅黑"14"bold"),                                 fg="#F39C12", bg="#0D0D1A")          title_label.pack(side=tk.LEFT, padx=16, pady=12)  self.status_label = tk.Label(header, text="● 运行中",                                       font=("微软雅黑"10),                                       fg="#2ECC71", bg="#0D0D1A")  self.status_label.pack(side=tk.RIGHT, padx=16)  # IO面板  self.io_panel = SearchableIOPanel(selfself.io_config)  self.io_panel.pack(fill=tk.BOTH, expand=True)  # 状态栏          statusbar = tk.Frame(self, bg="#0D0D1A", height=30)          statusbar.pack(fill=tk.X, side=tk.BOTTOM)          statusbar.pack_propagate(False)  self.bottom_stats = tk.Label(statusbar, text="", font=("Consolas"9),                                       fg="#95A5A6", bg="#0D0D1A")  self.bottom_stats.pack(side=tk.LEFT, padx=12, pady=6)  def_start_simulation(self):  defsimulate():  whileTrue:  try:  for addr inlist(self.io_states.keys()):  if random.random() < 0.1:  self.io_states[addr] = 1 - self.io_states[addr]  self.after(0self._update_ui)                      time.sleep(0.8)  except Exception as e:  print(f"[模拟线程错误] {e}")  break        thread = threading.Thread(target=simulate, daemon=True)          thread.start()  def_update_ui(self):  try:  self.io_panel.update_states(self.io_states)              active_count = sum(self.io_states.values())              total_count = len(self.io_states)  self.bottom_stats.config(                  text=f"激活: {active_count}  |  "f"总数: {total_count}  |  "f"更新时间: {time.strftime('%H:%M:%S')}"            )  except Exception as e:  print(f"[UI更新错误] {e}")  defmain():  try:          app = IOMonitorApp()  # 快捷键          app.bind("<Control-f>"lambda e: app.io_panel.search_entry.focus_set() if app.io_panel.search_entry elseNone)          app.bind("<Escape>"lambda e: app.io_panel._clear_search())          app.mainloop()  except Exception as e:  print(f"[程序启动错误] {e}")  input("按回车键退出...")  if __name__ == "__main__":      main()

这一版扩展性强多了。JSON配置文件可以由运维人员维护,开发和配置彻底解耦。我在一个汽车焊装线项目里用过类似思路,300多个IO点位,现场工程师自己就能改配置、加描述,完全不需要找我。


⚡ 方案三:工程款——Modbus实时对接

前两个方案都是"模拟数据",真正上生产,得接真实协议。这里给出Modbus TCP的接入骨架,用pymodbus库:

# 安装依赖:pip install pymodbusfrom pymodbus.client import ModbusTcpClientimport threading, timeclassModbusIOReader:"""Modbus TCP IO读取器,线程安全设计"""def__init__(self, host: str, port: int = 502):self.client = ModbusTcpClient(host, port=port)self._state: dict[strint] = {}self._lock = threading.Lock()self._running = Falsedefconnect(self) -> bool:returnself.client.connect()defstart_polling(self, interval: float = 0.5):"""启动后台轮询"""self._running = True        t = threading.Thread(target=self._poll_loop,                             args=(interval,), daemon=True)        t.start()def_poll_loop(self, interval: float):whileself._running:try:# 读线圈(DI/DO):地址0起,读16个点                result = self.client.read_coils(016, slave=1)ifnot result.isError():withself._lock:for i, bit inenumerate(result.bits[:16]):self._state[f"DI_{i:04d}"] = int(bit)except Exception as e:print(f"[Modbus] 读取异常: {e}")            time.sleep(interval)defget_state(self) -> dict:"""线程安全地获取当前状态快照"""withself._lock:returndict(self._state)defstop(self):self._running = Falseself.client.close()# 用法示意(接入前面的IOPanelApp):# reader = ModbusIOReader("192.168.1.100")# if reader.connect():#     reader.start_polling(0.5)# 然后在_refresh_ui里改成:io_state.update(reader.get_state())

💡 扩展建议:除了Modbus,工控现场还常见OPC-UA(用opcua库)、MQTT(用paho-mqtt)、西门子S7协议(用python-snap7)。接入方式大同小异,把ModbusIOReader这层换掉就行,上层界面完全不用动。这就是分层设计的好处。


🎯 三句话总结(建议截图收藏)

① 映射先行:IO可视化的核心不是界面有多花,而是地址→语义的映射建得有没有。

② after()是命根:Tkinter多线程,后台算数据,after(0, callback)回主线程改界面,这条规矩不能破。

③ 分组分层:点位按功能区域分组,逻辑和界面分层,后期维护才不抓狂。


📚 学习路线图

如果你想在这条路上走得更远,推荐这个顺序:

  1. 1. Tkinter进阶 → Canvas绘图、自定义控件、ttk主题定制
  2. 2. 工控协议 → Modbus TCP/RTU → OPC-UA → S7协议
  3. 3. 数据持久化 → SQLite记录历史状态 → 趋势图(matplotlib嵌入Tkinter)
  4. 4. 架构升级 → MVC模式重构 → 考虑迁移到PyQt5/PySide6(性能更强)
  5. 5. 部署实战 → PyInstaller打包 → 开机自启 → 日志系统

💬 互动时间

留两个话题,欢迎评论区聊:

  1. 1. 你的项目IO点位大概有多少个?用什么协议接入的? 西门子、欧姆龙、汇川,各家有各家的坑,大家互通有无。
  2. 2. 小挑战:试着改造方案一,给每个IO点位加一个"点击切换模拟状态"的功能(仅用于调试模式)。有思路了可以在评论区分享代码片段。

文章干货不少,如果对你有用,转发给身边做工控上位机的朋友——这种资料找起来真的费劲,直接转省很多事。也欢迎关注,后续还有Tkinter工控专项的更多实战内容持续更新。


#Python工控开发#Tkinter实战#IO点位监控#上位机开发#工业自动化

最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-03-01 03:09:24 HTTP/2.0 GET : https://f.mffb.com.cn/a/477169.html
  2. 运行时间 : 0.076735s [ 吞吐率:13.03req/s ] 内存消耗:4,979.35kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=0e7a8e4178bb51a18e80f6e0a61b1a7b
  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.000976s ] mysql:host=127.0.0.1;port=3306;dbname=f_mffb;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.001127s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000403s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000275s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.000633s ]
  6. SELECT * FROM `set` [ RunTime:0.000195s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.000607s ]
  8. SELECT * FROM `article` WHERE `id` = 477169 LIMIT 1 [ RunTime:0.000601s ]
  9. UPDATE `article` SET `lasttime` = 1772305764 WHERE `id` = 477169 [ RunTime:0.001057s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 66 LIMIT 1 [ RunTime:0.000226s ]
  11. SELECT * FROM `article` WHERE `id` < 477169 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.000441s ]
  12. SELECT * FROM `article` WHERE `id` > 477169 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.000402s ]
  13. SELECT * FROM `article` WHERE `id` < 477169 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.002955s ]
  14. SELECT * FROM `article` WHERE `id` < 477169 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.001299s ]
  15. SELECT * FROM `article` WHERE `id` < 477169 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.000962s ]
0.078214s