当前位置:首页>python>Python练字字帖生成工具 v1.0

Python练字字帖生成工具 v1.0

  • 2026-07-03 14:41:44
Python练字字帖生成工具 v1.0

✏️ 练字字帖生成工具 v1.0 — 让每一笔都有章可循

一款基于 Python Tkinter + Pillow 打造的桌面级练字字帖生成器,支持普通字帖与生字本双模式,内置回宫格/田字格/米字格,可自动生成示范字描红、留空临摹格与重复练习页,一键导出高清 PNG 与多页 PDF,是小学语文教学、书法启蒙和日常练字的得力助手。


✨ 开场白

汉字书写是中华文化传承的根基。无论是小学生初学写字,还是成年人重拾毛笔修身养性,一本好的字帖都是不可或缺的伴侣。然而市面上的字帖工具往往存在诸多不便:在线工具需要网络且格式受限,专业排版软件学习成本高昂,而手工制作字帖更是费时费力。

练字字帖生成工具正是为解决这些痛点而诞生的桌面应用。它完全基于 Python 生态构建,仅依赖 Tkinter 标准库和 Pillow 图像处理库,一个 .py 文件即可在 Windows/macOS/Linux 上运行,无需安装任何额外设计软件。

从教学场景出发,本工具精心设计了两种核心模式:普通排版模式适合整首诗词或段落的连续练习,每个字按设定次数重复示范后紧跟留空临摹格,形成"看一写一"的自然节奏;生字本模式则为每个生字分配独立一行,前几格为描红示范,后续全部留空供反复练习,特别适合低年级学生逐字攻克。

格子系统方面,工具内置了书法教学中最常用的三种格式——回宫格(带内框辅助定位笔画结构)、田字格(横竖中线划分四象限)和米字格(增加对角线引导撇捺走势),每种格子都经过精心绘制,线条粗细、颜色层次分明,既美观又实用。

附加功能上,工具支持自动标注汉字拼音(含声调)、示范格描红样式(红色半透明字迹引导临摹)、自定义标题与学生姓名、灵活的格子大小与边距调节、实时预览与翻页浏览,最终可导出 300DPI 高清 PNG 单页或完整多页 PDF 文件,直接打印即可使用。

整个项目约 400 行精炼的 Python 代码,结构清晰、逻辑分明,既是一个实用的教学辅助工具,也是学习 Tkinter GUI 开发、Pillow 图像绘制、坐标计算与文件导出的优秀实战案例。


📋 功能特性一览

模块
功能
排版模式
普通排版(连续练习)/ 生字本(逐字专练)
格子类型
回宫格 / 田字格 / 米字格
示范系统
描红样式(红色半透明)/ 标准黑字示范
临摹设计
可配置留空格数,极淡提示字引导书写
拼音标注
自动显示汉字拼音(含声调)
参数调节
每行格数/格子大小/页面边距/重复次数均可调
实时预览
所见即所得,支持多页翻页浏览
导出功能
300DPI PNG 单页导出 / 多页 PDF 导出
界面设计
清爽白色主题,左控制右预览双栏布局

🏗️ 第一部分:架构设计与数据模型

整体架构

程序采用单类 CalligraphyApp 封装所有逻辑,内部按职责划分为:

  • UI 构建层_build_ui / _build_control_panel / _build_preview_panel
  • 排版引擎_layout_normal / _layout_shengzi 生成行数据
  • 渲染引擎_render_page / _draw_row / _draw_grid_cell / _draw_char
  • 导出接口_export_png / _export_pdf

拼音数据

内置常用汉字拼音映射表,支持声调标注:

PINYIN_MAP = {
'春''chūn''眠''mián''不''bù''觉''jué''晓''xiǎo',
'处''chù''闻''wén''啼''tí''鸟''niǎo',
'夜''yè''来''lái''风''fēng''雨''yǔ''声''shēng',
'花''huā''落''luò''知''zhī''多''duō''少''shǎo',
}

排版数据结构

每一行由元组列表表示,每个元组为 (cell_type, char)

  • ("demo", "春") — 示范格,显示描红或标准字
  • ("blank", "春") — 临摹格,显示极淡提示字
  • ("empty", "") — 空白填充格

🎯 第二部分:排版引擎与格子绘制

普通排版模式

每个字按顺序排列,先重复 N 次示范格,再跟 M 个留空临摹格,自动换行:

def_layout_normal(self, chars):
    rows = []
    current_row = []
for ch in chars:
for _ in range(self.demo_repeat):      # 示范格 x N
            current_row.append(("demo", ch))
if len(current_row) >= self.cols_per_row:
                rows.append(current_row)
                current_row = []
for _ in range(self.blank_count):       # 临摹格 x M
            current_row.append(("blank", ch))
if len(current_row) >= self.cols_per_row:
                rows.append(current_row)
                current_row = []
return rows

生字本模式

每个字独占一行,前面是示范格,后面全部留空:

def_layout_shengzi(self, chars):
    rows = []
for ch in chars:
        row = []
for _ in range(self.demo_repeat):
            row.append(("demo", ch))
while len(row) < self.cols_per_row:
            row.append(("blank", ch))
        rows.append(row[:self.cols_per_row])
return rows

三种格子绘制

格子绘制的核心在于辅助线的差异:

def_draw_grid_cell(self, draw, x, y, size):
    draw.rectangle([x, y, x + size, y + size], outline="
#333333", width=2)
    cx, cy = x + size // 2, y + size // 2

if self.grid_type == "田字格":
        draw.line([(x, cy), (x + size, cy)], fill="#cccccc", width=1)
        draw.line([(cx, y), (cx, y + size)], fill="#cccccc", width=1)

elif self.grid_type == "米字格":
# 田字格基础 + 对角线
        draw.line([(x, cy), (x + size, cy)], fill="#cccccc", width=1)
        draw.line([(cx, y), (cx, y + size)], fill="#cccccc", width=1)
        draw.line([(x, y), (x + size, y + size)], fill="#cccccc", width=1)
        draw.line([(x + size, y), (x, y + size)], fill="#cccccc", width=1)

elif self.grid_type == "回宫格":
# 田字格基础 + 内框(1/3处)
        draw.line([(x, cy), (x + size, cy)], fill="#cccccc", width=1)
        draw.line([(cx, y), (cx, y + size)], fill="#cccccc", width=1)
        inner = size // 3
        draw.rectangle([x + inner, y + inner, x + size - inner, y + size - inner],
                       outline="#dddddd", width=1)

🖨️ 第三部分:渲染引擎与导出系统

页面渲染流程

_render_page 方法是整个工具的核心,完成从参数到图像的完整转换:

  1. 读取界面参数,提取有效汉字
  2. 根据模式调用排版引擎生成行数据
  3. 计算分页,确定当前页包含的行
  4. 创建白色背景 PIL Image
  5. 绘制标题区(标题 + 姓名 + 分隔线)
  6. 逐行绘制:拼音 → 格子 → 文字
  7. 绘制页码

描红样式实现

描红效果通过红色字体颜色实现视觉引导:

if self.use_miaohong:
    draw.text((tx, ty), ch, fill="#ffcccc", font=char_font)  # 底层浅红
    draw.text((tx, ty), ch, fill="#e74c3c", font=char_font)  # 上层红色
else:
    draw.text((tx, ty), ch, fill="#333333", font=char_font)   # 标准黑字

留空临摹格则使用极淡灰色 #f0f0f0 作为书写提示,打印后几乎不可见但能引导笔画走向。

PDF 多页导出

利用 Pillow 的 save_all 参数实现多页 PDF 一次性导出:

images[0].save(path, "PDF", resolution=300, save_all=True,
              append_images=images[1:])

💻 完整源代码


"""
练字字帖生成工具 v1.0
功能:支持普通字帖和生字本模式,可制作示范字、留空临摹格和重复练习页
支持回宫格/田字格/米字格,拼音显示,描红样式,PDF/PNG导出
"""


import tkinter as tk
from tkinter import ttk, filedialog, messagebox, simpledialog
from PIL import Image, ImageDraw, ImageFont, ImageTk
import math
import os

# ============ 拼音数据 ============
PINYIN_MAP = {
'春''chūn''眠''mián''不''bù''觉''jué''晓''xiǎo',
'处''chù''闻''wén''啼''tí''鸟''niǎo',
'夜''yè''来''lái''风''fēng''雨''yǔ''声''shēng',
'花''huā''落''luò''知''zhī''多''duō''少''shǎo',
}


classCalligraphyApp:
def__init__(self, root):
        self.root = root
        self.root.title("练字字帖生成工具 v1.0")
        self.root.geometry("1300x850")
        self.root.configure(bg="#f0f0f0")

# 默认参数
        self.content = "春眠不觉晓 处处闻啼鸟 夜来风雨声 花落知多少"
        self.mode = "普通排版"# 普通排版 / 生字本
        self.title_text = "小学生练字字帖"
        self.student_name = ""
        self.grid_type = "回宫格"# 回宫格 / 田字格 / 米字格
        self.cols_per_row = 8
        self.cell_size = 180
        self.page_margin = 120
        self.demo_repeat = 2
        self.blank_count = 2
        self.current_page = 1
        self.show_pinyin = True
        self.use_miaohong = True# 描红样式

# 预览图缓存
        self._preview_img = None
        self._preview_tk = None

        self._build_ui()
        self._generate_preview()

def_build_ui(self):
# 顶部标题
        header = tk.Frame(self.root, bg="#1a73e8", height=50)
        header.pack(fill=tk.X)
        header.pack_propagate(False)
        tk.Label(header, text="✏️ 练字字帖生成工具", font=("SimHei"16"bold"),
                 bg="#1a73e8", fg="white").pack(side=tk.LEFT, padx=20, pady=10)

# 主体区域
        body = tk.Frame(self.root, bg="#f0f0f0")
        body.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

# 左侧控制面板
        self._build_control_panel(body)

# 右侧预览区
        self._build_preview_panel(body)

def_build_control_panel(self, parent):
        ctrl = tk.Frame(parent, bg="white", width=340, relief=tk.RIDGE, bd=1)
        ctrl.pack(side=tk.LEFT, fill=tk.Y, padx=(010))
        ctrl.pack_propagate(False)

# 滚动支持
        canvas = tk.Canvas(ctrl, bg="white", highlightthickness=0)
        scrollbar = ttk.Scrollbar(ctrl, orient="vertical", command=canvas.yview)
        scroll_frame = tk.Frame(canvas, bg="white")

        scroll_frame.bind("<Configure>"lambda e: canvas.configure(scrollregion=canvas.bbox("all")))
        canvas.create_window((00), window=scroll_frame, anchor="nw")
        canvas.configure(yscrollcommand=scrollbar.set)

        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        pad = {"padx"12"pady"4}

# === 内容输入 ===
        tk.Label(scroll_frame, text="📝 练字内容", font=("SimHei"10"bold"),
                 bg="white").pack(anchor="w", **pad)
        self.content_text = tk.Text(scroll_frame, height=4, width=36, font=("SimSun"11),
                                    wrap=tk.WORD, relief=tk.SOLID, bd=1)
        self.content_text.pack(**pad)
        self.content_text.insert("1.0", self.content)

# === 版式模式 ===
        tk.Label(scroll_frame, text="📐 版式模式", font=("SimHei"10"bold"),
                 bg="white").pack(anchor="w", **pad)
        self.mode_var = tk.StringVar(value=self.mode)
        mode_frame = tk.Frame(scroll_frame, bg="white")
        mode_frame.pack(anchor="w", **pad)
for m in ["普通排版""生字本"]:
            tk.Radiobutton(mode_frame, text=m, variable=self.mode_var, value=m,
                          bg="white", font=("SimSun"10)).pack(side=tk.LEFT, padx=8)

# === 标题 ===
        tk.Label(scroll_frame, text="标题", bg="white", font=("SimSun"10)).pack(anchor="w", **pad)
        self.title_entry = tk.Entry(scroll_frame, font=("SimSun"11), width=30, relief=tk.SOLID, bd=1)
        self.title_entry.pack(**pad)
        self.title_entry.insert(0, self.title_text)

# === 姓名 ===
        tk.Label(scroll_frame, text="姓名(可选)", bg="white", font=("SimSun"10)).pack(anchor="w", **pad)
        self.name_entry = tk.Entry(scroll_frame, font=("SimSun"11), width=30, relief=tk.SOLID, bd=1)
        self.name_entry.pack(**pad)

# === 格子类型 ===
        tk.Label(scroll_frame, text="格子类型", bg="white", font=("SimSun"10)).pack(anchor="w", **pad)
        self.grid_var = tk.StringVar(value=self.grid_type)
        grid_frame = tk.Frame(scroll_frame, bg="white")
        grid_frame.pack(anchor="w", **pad)
for g in ["回宫格""田字格""米字格"]:
            tk.Radiobutton(grid_frame, text=g, variable=self.grid_var, value=g,
                          bg="white", font=("SimSun"10)).pack(side=tk.LEFT, padx=6)

# === 数值参数 ===
        params = [
            ("每行格数""cols_spin"416, self.cols_per_row),
            ("格子大小""size_spin"80300, self.cell_size),
            ("页面边距""margin_spin"40200, self.page_margin),
            ("示范重复次数""demo_spin"18, self.demo_repeat),
            ("留空临摹格""blank_spin"010, self.blank_count),
        ]

for label, attr, from_, to_, default in params:
            frame = tk.Frame(scroll_frame, bg="white")
            frame.pack(fill=tk.X, **pad)
            tk.Label(frame, text=label, bg="white", font=("SimSun"10), width=12,
                     anchor="w").pack(side=tk.LEFT)
            spin = ttk.Spinbox(frame, from_=from_, to=to_, width=8, font=("SimSun"10))
            spin.set(default)
            spin.pack(side=tk.LEFT, padx=4)
            setattr(self, attr, spin)

# === 附加选项 ===
        tk.Label(scroll_frame, text="📌 附加选项", font=("SimHei"10"bold"),
                 bg="white").pack(anchor="w", **pad)
        self.pinyin_var = tk.BooleanVar(value=True)
        tk.Checkbutton(scroll_frame, text="显示拼音", variable=self.pinyin_var,
                      bg="white", font=("SimSun"10)).pack(anchor="w", **pad)
        self.miaohong_var = tk.BooleanVar(value=True)
        tk.Checkbutton(scroll_frame, text="示范格使用描红样式", variable=self.miaohong_var,
                      bg="white", font=("SimSun"10)).pack(anchor="w", **pad)

# === 操作按钮 ===
        btn_frame = tk.Frame(scroll_frame, bg="white")
        btn_frame.pack(fill=tk.X, pady=15, padx=12)

        tk.Button(btn_frame, text="🔄 生成预览", command=self._generate_preview,
                 font=("SimHei"11), bg="#1a73e8", fg="white", relief=tk.FLAT,
                 padx=15, pady=6).pack(fill=tk.X, pady=3)
        tk.Button(btn_frame, text="💾 导出PNG", command=self._export_png,
                 font=("SimHei"11), bg="#34a853", fg="white", relief=tk.FLAT,
                 padx=15, pady=6).pack(fill=tk.X, pady=3)
        tk.Button(btn_frame, text="📄 导出PDF", command=self._export_pdf,
                 font=("SimHei"11), bg="#ea4335", fg="white", relief=tk.FLAT,
                 padx=15, pady=6).pack(fill=tk.X, pady=3)

def_build_preview_panel(self, parent):
        preview_frame = tk.Frame(parent, bg="white", relief=tk.RIDGE, bd=1)
        preview_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

# 预览标题栏
        top_bar = tk.Frame(preview_frame, bg="#f8f9fa", height=40)
        top_bar.pack(fill=tk.X)
        top_bar.pack_propagate(False)
        tk.Label(top_bar, text="预览", font=("SimHei"10"bold"),
                 bg="#f8f9fa").pack(side=tk.LEFT, padx=10, pady=8)

# 页码控制
        page_frame = tk.Frame(top_bar, bg="#f8f9fa")
        page_frame.pack(side=tk.RIGHT, padx=10)
        tk.Button(page_frame, text="◀", command=self._prev_page, width=3,
                 relief=tk.FLAT, bg="#e0e0e0").pack(side=tk.LEFT, padx=2)
        self.page_label = tk.Label(page_frame, text="第 1 页", bg="#f8f9fa",
                                   font=("SimSun"10))
        self.page_label.pack(side=tk.LEFT, padx=8)
        tk.Button(page_frame, text="▶", command=self._next_page, width=3,
                 relief=tk.FLAT, bg="#e0e0e0").pack(side=tk.LEFT, padx=2)

# 画布预览
        self.preview_canvas = tk.Canvas(preview_frame, bg="#e0e0e0", highlightthickness=0)
        self.preview_canvas.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

def_prev_page(self):
if self.current_page > 1:
            self.current_page -= 1
            self._generate_preview()

def_next_page(self):
        self.current_page += 1
        self._generate_preview()

def_read_params(self):
"""从界面读取当前参数"""
        self.content = self.content_text.get("1.0", tk.END).strip()
        self.mode = self.mode_var.get()
        self.title_text = self.title_entry.get().strip()
        self.student_name = self.name_entry.get().strip()
        self.grid_type = self.grid_var.get()
        self.cols_per_row = int(self.cols_spin.get())
        self.cell_size = int(self.size_spin.get())
        self.page_margin = int(self.margin_spin.get())
        self.demo_repeat = int(self.demo_spin.get())
        self.blank_count = int(self.blank_spin.get())
        self.show_pinyin = self.pinyin_var.get()
        self.use_miaohong = self.miaohong_var.get()

def_get_chars(self):
"""提取练字字符(去除空格和标点)"""
        chars = []
for ch in self.content:
if ch.strip() and'\u4e00' <= ch <= '\u9fff':
                chars.append(ch)
return chars

def_get_font(self, size, bold=False):
"""获取字体"""
        weight = "bold"if bold else"normal"
try:
return ImageFont.truetype("simkai.ttf", size)
except:
try:
return ImageFont.truetype("simsun.ttc", size)
except:
try:
return ImageFont.truetype("msyh.ttc", size)
except:
return ImageFont.load_default()

def_get_pinyin_font(self, size):
"""获取拼音字体"""
try:
return ImageFont.truetype("arial.ttf", size)
except:
try:
return ImageFont.truetype("msyh.ttc", size)
except:
return ImageFont.load_default()

def_render_page(self, page_num=1):
"""渲染指定页面为 PIL Image"""
        self._read_params()
        chars = self._get_chars()

# 页面尺寸 (A4 竖向 300DPI 等效)
        page_w = self.page_margin * 2 + self.cols_per_row * self.cell_size
# 计算行高
        row_height = self.cell_size
if self.show_pinyin:
            row_height = self.cell_size + 40# 拼音区域

# 标题区高度
        header_h = 100

# 每页可容纳行数
        available_h = 2800# 固定页面高度
        rows_per_page = max(1, (available_h - header_h - self.page_margin * 2) // row_height)

# 根据模式生成行数据
if self.mode == "普通排版":
            all_rows = self._layout_normal(chars)
else:
            all_rows = self._layout_shengzi(chars)

# 总页数
        total_pages = max(1, math.ceil(len(all_rows) / rows_per_page))
if page_num > total_pages:
            page_num = total_pages
            self.current_page = page_num

# 当前页的行
        start_row = (page_num - 1) * rows_per_page
        end_row = min(start_row + rows_per_page, len(all_rows))
        page_rows = all_rows[start_row:end_row]

# 计算实际页面高度
        page_h = header_h + self.page_margin * 2 + len(page_rows) * row_height + 60

# 创建图像
        img = Image.new("RGB", (page_w, page_h), "#FFFFFF")
        draw = ImageDraw.Draw(img)

# 绘制标题
        self._draw_header(draw, page_w, header_h)

# 绘制格子和文字
        y_offset = header_h + self.page_margin
for row_data in page_rows:
            self._draw_row(draw, img, row_data, self.page_margin, y_offset, page_w)
            y_offset += row_height

# 绘制页码
        page_font = self._get_font(24)
        page_text = f"第 {page_num} / {total_pages} 页"
        bbox = draw.textbbox((00), page_text, font=page_font)
        tw = bbox[2] - bbox[0]
        draw.text(((page_w - tw) // 2, page_h - 50), page_text,
                  fill="#999999", font=page_font)

        self.page_label.config(text=f"第 {page_num} / {total_pages} 页")
return img

def_layout_normal(self, chars):
"""普通排版:每个字重复 demo_repeat 次 + blank_count 个空格"""
        rows = []
        current_row = []
for ch in chars:
# 示范格(描红)
for _ in range(self.demo_repeat):
                current_row.append(("demo", ch))
if len(current_row) >= self.cols_per_row:
                    rows.append(current_row)
                    current_row = []
# 留空临摹格
for _ in range(self.blank_count):
                current_row.append(("blank", ch))
if len(current_row) >= self.cols_per_row:
                    rows.append(current_row)
                    current_row = []
if current_row:
# 补齐空格
while len(current_row) < self.cols_per_row:
                current_row.append(("empty"""))
            rows.append(current_row)
return rows

def_layout_shengzi(self, chars):
"""生字本模式:每个字占一整行,先示范后留空"""
        rows = []
for ch in chars:
            row = []
for _ in range(self.demo_repeat):
                row.append(("demo", ch))
for _ in range(self.blank_count):
                row.append(("blank", ch))
# 剩余填空格
while len(row) < self.cols_per_row:
                row.append(("blank", ch))
            rows.append(row[:self.cols_per_row])
return rows

def_draw_header(self, draw, page_w, header_h):
"""绘制页面标题区"""
# 标题
        title_font = self._get_font(36, bold=True)
if self.title_text:
            bbox = draw.textbbox((00), self.title_text, font=title_font)
            tw = bbox[2] - bbox[0]
            draw.text(((page_w - tw) // 225), self.title_text,
                      fill="#333333", font=title_font)

# 姓名
if self.student_name:
            name_font = self._get_font(24)
            name_text = f"姓名:{self.student_name}"
            draw.text((self.page_margin, 75), name_text, fill="#666666", font=name_font)

# 分隔线
        draw.line([(self.page_margin, header_h - 5),
                   (page_w - self.page_margin, header_h - 5)],
                  fill="#cccccc", width=2)

def_draw_row(self, draw, img, row_data, x_start, y_start, page_w):
"""绘制一行格子"""
        cell = self.cell_size
        pinyin_h = 40if self.show_pinyin else0

for col_idx, (cell_type, ch) in enumerate(row_data):
            x = x_start + col_idx * cell
            y = y_start + pinyin_h

# 绘制拼音
if self.show_pinyin and ch and cell_type == "demo":
                pinyin = PINYIN_MAP.get(ch, "")
if pinyin:
                    py_font = self._get_pinyin_font(20)
                    bbox = draw.textbbox((00), pinyin, font=py_font)
                    pw = bbox[2] - bbox[0]
                    draw.text((x + (cell - pw) // 2, y_start + 5), pinyin,
                              fill="#e74c3c", font=py_font)

# 绘制格子
            self._draw_grid_cell(draw, x, y, cell)

# 绘制文字
if cell_type == "demo"and ch:
                self._draw_char(draw, ch, x, y, cell, is_demo=True)
elif cell_type == "blank"and ch:
# 留空格只画淡色提示
                self._draw_char(draw, ch, x, y, cell, is_demo=False)

def_draw_grid_cell(self, draw, x, y, size):
"""绘制单个格子"""
# 外框
        draw.rectangle([x, y, x + size, y + size], outline="#333333", width=2)

        grid_type = self.grid_type
        cx, cy = x + size // 2, y + size // 2

if grid_type == "田字格":
# 横竖中线
            draw.line([(x, cy), (x + size, cy)], fill="#cccccc", width=1)
            draw.line([(cx, y), (cx, y + size)], fill="#cccccc", width=1)

elif grid_type == "米字格":
# 横竖中线 + 对角线
            draw.line([(x, cy), (x + size, cy)], fill="#cccccc", width=1)
            draw.line([(cx, y), (cx, y + size)], fill="#cccccc", width=1)
            draw.line([(x, y), (x + size, y + size)], fill="#cccccc", width=1)
            draw.line([(x + size, y), (x, y + size)], fill="#cccccc", width=1)

elif grid_type == "回宫格":
# 横竖中线
            draw.line([(x, cy), (x + size, cy)], fill="#cccccc", width=1)
            draw.line([(cx, y), (cx, y + size)], fill="#cccccc", width=1)
# 内框(1/3处)
            inner_margin = size // 3
            ix1 = x + inner_margin
            iy1 = y + inner_margin
            ix2 = x + size - inner_margin
            iy2 = y + size - inner_margin
            draw.rectangle([ix1, iy1, ix2, iy2], outline="#dddddd", width=1)

def_draw_char(self, draw, ch, x, y, size, is_demo=True):
"""绘制字符"""
        font_size = int(size * 0.7)
        char_font = self._get_font(font_size)

        bbox = draw.textbbox((00), ch, font=char_font)
        cw = bbox[2] - bbox[0]
        ch_h = bbox[3] - bbox[1]

        tx = x + (size - cw) // 2
        ty = y + (size - ch_h) // 2 - bbox[1]

if is_demo:
if self.use_miaohong:
# 描红样式:红色半透明
                draw.text((tx, ty), ch, fill="#ffcccc", font=char_font)
# 再画一层轮廓感
                draw.text((tx, ty), ch, fill="#e74c3c", font=char_font)
else:
                draw.text((tx, ty), ch, fill="#333333", font=char_font)
else:
# 留空临摹:极淡提示
            draw.text((tx, ty), ch, fill="#f0f0f0", font=char_font)

def_generate_preview(self):
"""生成预览"""
try:
            img = self._render_page(self.current_page)
            self._preview_img = img

# 缩放适配预览区
            canvas_w = self.preview_canvas.winfo_width()
            canvas_h = self.preview_canvas.winfo_height()
if canvas_w < 100:
                canvas_w = 800
if canvas_h < 100:
                canvas_h = 700

            img_w, img_h = img.size
            scale = min(canvas_w / img_w, canvas_h / img_h, 1.0) * 0.95
            new_w = int(img_w * scale)
            new_h = int(img_h * scale)

            preview = img.resize((new_w, new_h), Image.LANCZOS)
            self._preview_tk = ImageTk.PhotoImage(preview)

            self.preview_canvas.delete("all")
            cx = canvas_w // 2
            cy = canvas_h // 2
# 阴影
            self.preview_canvas.create_rectangle(cx - new_w // 2 + 4, cy - new_h // 2 + 4,
                                                  cx + new_w // 2 + 4, cy + new_h // 2 + 4,
                                                  fill="#cccccc", outline="")
            self.preview_canvas.create_image(cx, cy, image=self._preview_tk)
except Exception as e:
            messagebox.showerror("预览错误", str(e))

def_export_png(self):
"""导出PNG"""
        self._read_params()
        path = filedialog.asksaveasfilename(defaultextension=".png",
                                            filetypes=[("PNG图片""*.png")])
ifnot path:
return
try:
            img = self._render_page(self.current_page)
            img.save(path, dpi=(300300))
            messagebox.showinfo("导出成功"f"已保存到:\n{path}")
except Exception as e:
            messagebox.showerror("导出失败", str(e))

def_export_pdf(self):
"""导出PDF(所有页)"""
        self._read_params()
        path = filedialog.asksaveasfilename(defaultextension=".pdf",
                                            filetypes=[("PDF文件""*.pdf")])
ifnot path:
return
try:
            chars = self._get_chars()
if self.mode == "普通排版":
                all_rows = self._layout_normal(chars)
else:
                all_rows = self._layout_shengzi(chars)

            row_height = self.cell_size + (40if self.show_pinyin else0)
            rows_per_page = max(1, (2800 - 100 - self.page_margin * 2) // row_height)
            total_pages = max(1, math.ceil(len(all_rows) / rows_per_page))

            images = []
for p in range(1, total_pages + 1):
                img = self._render_page(p)
if img.mode == "RGBA":
                    bg = Image.new("RGB", img.size, (255255255))
                    bg.paste(img, mask=img.split()[3])
                    img = bg
                images.append(img)

if images:
                images[0].save(path, "PDF", resolution=300, save_all=True,
                              append_images=images[1:])
                messagebox.showinfo("导出成功"f"PDF已保存到:\n{path}\n共 {total_pages} 页")
except Exception as e:
            messagebox.showerror("导出失败", str(e))


# ============ 程序入口 ============
if __name__ == "__main__":
    root = tk.Tk()
    app = CalligraphyApp(root)
    root.mainloop()


知识点总结

1. Tkinter GUI 开发技巧

  • 双栏布局:左侧固定宽度控制面板 + 右侧自适应预览区,使用 pack(side=LEFT/RIGHT) 配合 pack_propagate(False) 实现
  • 滚动面板:通过 Canvas + Scrollbar + Frame 组合实现可滚动的参数面板
  • Spinbox 数值输入ttk.Spinbox 提供带范围限制的数值输入,比 Entry 更适合参数调节
  • RadioButton 分组:通过共享 StringVar 实现互斥选择(模式切换、格子类型)
  • CheckButton 开关BooleanVar 绑定实现功能开关(拼音显示、描红样式)
  • Canvas 图片显示ImageTk.PhotoImage 将 PIL Image 转为 Tkinter 可显示对象

2. Pillow 图像绘制

  • **Image.new()**:创建指定尺寸的空白画布
  • ImageDraw.rectangle/line/text:绘制几何图形和文字
  • **ImageFont.truetype()**:加载系统 TrueType 字体
  • **textbbox()**:精确计算文字边界框,用于居中对齐
  • resize() + LANCZOS:高质量图像缩放用于预览
  • PDF 多页保存save_all=True + append_images 实现多页 PDF

3. 坐标计算与排版算法

  • 格子定位x = margin + col_idx * cell_size,行列索引直接映射像素坐标
  • 文字居中:通过 textbbox 获取文字实际宽高,计算偏移量实现格内居中
  • 分页算法:总行数  每页行数 = 总页数,切片获取当前页数据
  • 自动换行:当前行满 cols_per_row 格时自动推入行列表并开新行

4. 字帖设计原理

  • 描红:红色半透明字迹作为书写引导,学生在其上覆写加深肌肉记忆
  • 临摹:极淡提示字(几乎不可见)引导笔画结构,培养独立书写能力
  • 回宫格:内框帮助定位字的重心和笔画分布,是书法教学的经典辅助工具
  • 重复练习:同一字多次书写形成肌肉记忆,符合运动学习的间隔重复原则

5. 软件工程实践

  • 参数集中管理:所有可调参数在 __init__ 中统一初始化,_read_params 统一读取
  • 渲染与 UI 分离_render_page 返回纯 PIL Image,不依赖 Tkinter,便于测试和复用
  • 容错设计:字体加载采用多级 fallback,确保在不同系统上都能正常运行
  • 预览缩放:根据画布实际尺寸动态计算缩放比例,适配不同屏幕分辨率

拓展场景与测试步骤

测试步骤

  1. 环境准备

    pip install Pillow
    python 练字字帖生成工具.py
  2. 界面验证

    • 启动后确认窗口正常显示(1300x850)
    • 左侧控制面板各控件可正常操作
    • 右侧预览区显示默认字帖内容
  3. 模式切换测试

    • 切换"普通排版":观察字符连续排列,每字2格示范+2格留空
    • 切换"生字本":观察每字独占一行,前2格描红后6格留空
  4. 格子类型测试

    • 回宫格:外框 + 十字线 + 内框
    • 田字格:外框 + 十字线
    • 米字格:外框 + 十字线 + 对角线
  5. 参数调节测试

    • 修改每行格数(4~16),观察布局变化
    • 调整格子大小(80~300),观察缩放效果
    • 修改示范次数和留空格数,验证排版逻辑
  6. 附加功能测试

    • 勾选/取消"显示拼音",验证拼音显示切换
    • 勾选/取消"描红样式",验证红色/黑色切换
    • 输入标题和姓名,验证页眉显示
  7. 导出测试

    • 导出 PNG:验证 300DPI 高清输出,格子清晰
    • 导出 PDF:验证多页完整输出,可正常打印

拓展场景

场景
说明
小学语文教学
教师输入本课生字,生成全班统一字帖打印分发
书法兴趣班
使用米字格模式,配合毛笔字体练习基本笔画
幼儿启蒙
大格子(250+)配合拼音,适合学龄前儿童描红
成人硬笔
小格子(100)普通排版,整首诗词连续练习
家庭作业
填入学生姓名,生成个性化练字作业
考前突击
生字本模式集中练习易错字

进阶优化方向

  • 接入完整拼音库(pypinyin),支持任意汉字自动注音
  • 添加笔顺动画演示功能
  • 支持自定义字体上传(楷体/行书/隶书)
  • 增加字帖模板(古诗词库、成语库、课文生字表)
  • 支持横向/竖向排版切换
  • 添加水印和页眉页脚自定义
  • 批量生成多份不同内容的字帖
  • 支持英文字母和数字练习模式

最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-07-03 14:48:20 HTTP/2.0 GET : https://f.mffb.com.cn/a/494953.html
  2. 运行时间 : 1.943208s [ 吞吐率:0.51req/s ] 内存消耗:4,758.43kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=66918edfe9a5741fc14b55f37e4c7fcc
  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.000607s ] mysql:host=127.0.0.1;port=3306;dbname=f_mffb;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.000774s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.008724s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.100685s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.000701s ]
  6. SELECT * FROM `set` [ RunTime:0.067850s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.000833s ]
  8. SELECT * FROM `article` WHERE `id` = 494953 LIMIT 1 [ RunTime:0.076011s ]
  9. UPDATE `article` SET `lasttime` = 1783061300 WHERE `id` = 494953 [ RunTime:0.032526s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 66 LIMIT 1 [ RunTime:0.005740s ]
  11. SELECT * FROM `article` WHERE `id` < 494953 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.027109s ]
  12. SELECT * FROM `article` WHERE `id` > 494953 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.018621s ]
  13. SELECT * FROM `article` WHERE `id` < 494953 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.022115s ]
  14. SELECT * FROM `article` WHERE `id` < 494953 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.906027s ]
  15. SELECT * FROM `article` WHERE `id` < 494953 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.578500s ]
1.944841s