Zhiz Print
> 出品维护 广德智兆科技有限公司 | 开发者 智兆.黄 +8613913019935 | 官方仓库 > > 适用版本 v15+ > > 基于 Frappe / ERPNext 的可视化打印模板设计器 — 表格设计 · 数据绑定 · 表达式运算 · 多引擎 PDF · 批量打印 · 持久化日志。
- Author: yowlion
- Repository: https://github.com/yowlion/zhiz_print
- GitHub stars: 0
- Forks: 1
- License: NOASSERTION
- Category: Other
- Maintenance: Actively Maintained
Install Zhiz Print
bench get-app https://github.com/yowlion/zhiz_print
Add the Frappe Gems badge to your README
Maintain Zhiz Print? Paste this into your README:
[](https://frappegems.com/gems/apps/yowlion/zhiz_print)
About Zhiz Print
zhiz_print · 高级打印设计器 使用说明书
> 出品维护 广德智兆科技有限公司 | 开发者 智兆.黄 +8613913019935 | 官方仓库 > > 适用版本 v15+ > > 基于 Frappe / ERPNext 的可视化打印模板设计器 — 表格设计 · 数据绑定 · 表达式运算 · 多引擎 PDF · 批量打印 · 持久化日志。
关于 Zhiz Print
Zhiz Print 由 广德智兆科技有限公司(Guangde Zhizhao Technology Co., Ltd.)出品并长期维护,开发人员 智兆.黄(Gitee @gdzhiz)全力迭代更新。
- 定位:适用于 ERPNext / Frappe 的复杂格式打印设计专用 App —— 表格网格所见即所得设计,专攻多页版面、单元格合并、表达式运算、条码/二维码、多引擎 PDF 导出等复杂打印格式
- 官方仓库:(唯一官方源,其余均为社区 fork)
- GitHub 镜像:(自动同步自 Gitee 官方源)
- 发布讨论:
- 版权:Copyright © 2026 广德智兆科技有限公司(Guangde Zhizhao Technology Co., Ltd.),保留所有权利,见 LICENSE
目录
- 概述与架构
- 安装与启用
- 设计器入门
- 数据绑定(占位符)
- 单元格值的四种语义
- 行控制与排序
- 单元格类型
- 分页与多页设计
- 页眉/页脚/左眉/右脚
- 启用条件
- 草稿拦截
- 输出(预览/PDF/Excel/批量)
- 打印日志
- 全局设置
- 打印模板平台
- 附录·速查表与常见问题
一、概述与架构
zhiz_print 用表格式网格设计打印版面,支持单元格合并、四种取值语义(静态 / 逻辑 / 表达式 / 合计)、按列排序、自动分页、二维码条码图片,以及多引擎 PDF / Excel 导出。所有打印操作记录为持久化日志(渲染快照,不随后续设计改动而变化)。
v15.22 起支持报表打印:设计目标可选「报表」(Report),ERPNext 报表(Query Report / Script Report,如库存余额 / 总账)同样可以用本设计器设计打印模板 —— 报表行绑定 rep 占位符、筛选值进页眉、合计用 =rowsum() 自行设计,与单据打印共用同一套渲染 / 分页 / 输出管线。
数据流
| 阶段 | 说明 | 关键代码 |
|---|---|---|
| 1. 数据获取 | 单据模式:从目标单据(doc)读主表字段、子表(如 items)、自定义查询(design_queries)、用户参数(param);报表模式:以当前用户身份同步执行报表查询,报表行注入为 __report_main__ 伪查询 |
frappe.get_doc · _execute_query_with_doc_context · zhiz_print.api.report_print._run_report |
| 2. 运算 | 四种单元格值语义:静态替换、logic 条件表达式、= 算术表达式、=rowsum() 合计 |
_eval_logic_code · _eval_expression_cell · _eval_rowsum |
| 3. 渲染 | 展开数据驱动行 → 按行级 sorts 排序 → 列值计算 → 客户端实测分页 → 组装 HTML |
_build_expanded_rows_v2 · _build_row_html · build_measurement_html |
| 4. 输出 | 打印预览 / PDF(wkhtmltopdf·WeasyPrint·Chromium) / Excel(openpyxl) / 批量 | render_print_preview · generate_print_pdf · export_print_excel |
DocType 清单
| DocType | 类型 | 作用 |
|---|---|---|
| Super Print Design | 主文档 | 打印模板设计(网格 + 单元格 + 样式) |
| Super Print Design Item | 子表 | 单元格定义(row/col/span/类型/值/page_no) |
| Super Print Design Parameter | 子表 | 打印参数定义(打印前弹窗填) |
| Super Print Design Query | 子表 | 数据查询定义(SQL/函数) |
| Super Print Paper | 主文档 | 纸张尺寸(mm) |
| Super Print Log | 主文档 | 打印日志(含渲染快照) |
| Super Print Enabled Doctype | 子表 | 启用超级打印的单据类型 |
| Super Print Enabled Report | 子表 | 启用超级打印的报表(报表打印指定启用) |
| Zprint Setting | 单文档 | 全局设置(单据/报表打印启用、PDF 引擎等) |
二、安装与启用
安装
cd /home/frappe/frappe-bench
git clone git@gitee.com:gdzhiz/zhiz_print.git apps/zhiz_print
bench get-app zhiz_print ./apps/zhiz_print
bench --site install-app zhiz_print
bench build
启用流程
- Zprint Setting → 勾选「启用超级打印页面」
- Super Print Paper → 定义纸张尺寸(如 A4 = 210×297mm)
- Super Print Enabled Doctype → 把要用的单据类型(如 Sales Invoice)加入启用列表
- Super Print Design → 设计模板,填
target_doctype、print_paper - 打开任意目标单据,点打印 → 左侧显示自定义模板选择器
报表打印启用 (v15.22 新)
- Zprint Setting →「报表打印」段 → 勾选「启用报表打印」
- 启用方式二选一:全部启用(所有报表) / 指定启用(在「启用的报表」子表加入目标报表,如 Stock Balance)
- 打开目标报表页(设好筛选、跑出数据)→ 菜单「打印」→ 直达高级打印预览页(原生 PDF / 导出菜单自动隐藏,导出功能收进预览页)
- 预览页「新建报表设计」→ 自动预填设计目标=报表 → 开始设计(见三、设计器入门·报表模式))
> 💡 报表打印与单据打印开关独立,互不影响;关闭开关即完整还原报表页原生菜单。
三、设计器入门
设计器是一个表格式网格编辑器:左侧行号、顶部列号,中间是单元格。上方工具栏控制行列增删、字体、行类型;选中行/列/单元格时右侧出现属性面板。
网格操作
| 操作 | 说明 | |||
|---|---|---|---|---|
| 选中整行 / 整列 | 点击左侧行号 / 顶部列号 → 右侧出现行/列属性面板 | |||
| 选中单元格 | 点击单元格 → 出现单元格属性面板(类型/值/样式) | |||
| 插入行 / 列 | 选中行/列后点工具栏「插入行」「插入列」(在选中位置左侧/上方插入) | |||
| 合并单元格 | 单元格属性面板设 rowspan / colspan(被合并的格子自动标记 `| |
MERGED::R#C# | `) | |
| 调整列宽 (新) | 鼠标移到顶部列号右边界变 ↔ 光标,按住左右拖动实时改列宽(列签与网格同步,拖动时鼠标旁实时显示列宽像素值),松开固化 |
演示预览 (新)
工具栏「演示预览」按钮一键把当前网格按 sample_doc 数据渲染成实际纸张效果(灰底白纸),无需进入打印页面;再点「回到设计」返回编辑。预览态禁用一切编辑操作。
页面设置入口
除「页面设置」按钮外,点击画布空白区域直接打开页面设置:点击纸张上半自动选中页眉页签、下半自动选中页脚页签(详见九、页眉/页脚)。
报表模式设计 (v15.22 新)
新建设计时「设计目标」可选 DocType(单据) 或 Report(报表):
- 选「报表」后填「目标报表」(如 Stock Balance),网格设计操作与单据模式完全一致
- 单元格绑定「数据查询」→ 查询选「报表数据」,数据键下拉分三类:
- 报表字段:
name/report_name/ref_doctype等(报表本身的属性) - 筛选字段:
filters.company/filters.from_date等(当前报表筛选) - 报表数据列:
items.item_code/items.bal_qty等(报表行数据)
- 报表字段:
- 选择数据键后单元格自动写入对应占位符并即时显示,如选「物料」→ 单元格显示
{rep.items.item_code} - 数据行:把行类型设为「数据驱动行」(或单元格含
{rep.items.字段}自动识别),打印时按报表行数自动展开 - 报表模式下「模板演示单据」「草稿拦截」等单据专属功能自动隐藏
三个核心区域(表单 Tab)
- Design View(设计视图) — 可视化网格编辑器
- Settings(设置) — 启用条件 / 草稿拦截 / 优先级 / 纸张 等
- Design Items — 单元格明细(只读,序列化的格子数据)
四、数据绑定(占位符)
在单元格的 cell_value 里用占位符绑定数据。占位符打印时被替换为实际值。
占位符
| 语法 | 数据来源 | 示例 |
|---|---|---|
{doc.字段名} |
目标单据主表字段 | {doc.name} · {doc.customer} · {doc.posting_date} |
{doc.子表.字段} |
子表行字段(自动触发数据驱动行) | {doc.items.item_code} · {doc.items.qty} |
{param.参数名} |
用户打印前填入的参数 | {param.remark} |
{查询名.列名} |
自定义查询结果(首行) | {bom_list.qty} |
{rep.字段} |
报表本身字段(报表模式) | {rep.name} · {rep.ref_doctype} |
{rep.filters.字段} |
当前报表筛选值(报表模式) | {rep.filters.company} · {rep.filters.from_date} |
{rep.items.字段} |
报表行数据(自动触发数据驱动行,报表模式) | {rep.items.item_code} · {rep.items.bal_qty} |
rep 报表占位符 (v15.22 新)
报表模式下 rep 与 doc / param 同级,三段语义:
| 写法 | 含义 |
|---|---|
{rep.name}(单级) |
报表本身字段(Report 文档:name / reportname / refdoctype / module 等) |
{rep.filters.字段} |
当前报表筛选值 —— 任意单元格与页眉页脚均可用,常用于打印标题区(公司 / 日期区间) |
{rep.items.字段} |
报表当前行数据 —— 仅数据驱动行内生效;含此占位符的行自动按报表行展开 |
> ⚠️ rep 是保留前缀,数据查询名称不能命名为 rep;报表打印会剔除报表引擎自动附加的合计行,合计请用 =rowsum() 自行设计(见五、单元格值语义)。
> 💡 富文本字段(Text Editor 类型,如 terms)打印时自动取纯文本显示,不输出 HTML 标签。
子表 → 数据驱动行
只要某行有单元格含 {doc.子表.字段},该行自动变成数据驱动行,按子表条数自动复制;每行对应一条子表记录。无需手动设 row_type。
{doc.items.idx} ← 行号
{doc.items.item_code} ← 物料
{doc.items.qty} ← 数量
{doc.items.rate} ← 单价
{doc.items.amount} ← 金额
参数(打印前弹窗)
在 design_parameters 子表定义参数,打印前弹窗让用户填写,用 {param.参数名} 引用。
| Parameter ID | Display Label | Type | 示例用途 |
|---|---|---|---|
| remark | 备注 | Data | 打印时手输一句话 |
| warehouse | 仓库 | Link → Warehouse | 选择仓库 |
| copies | 份数 | Int (reqd=1, default=1) | 打印份数 |
数据查询 (design_queries)
当需要跨表取数、聚合统计、复杂 SQL 而 {doc.字段} 和子表不够用时,在设计的 design_queries 子表定义查询,结果用 {query_name.column} 引用。
查询定义字段(Super Print Design Query)
| 字段 | 说明 |
|---|---|
Query Name (query_name) |
查询名,唯一标识,用于 {query_name.column} 引用 |
Python Code (query_code) |
Python 代码或函数路径,结果必须赋给变量 result |
Parameters (parameters) |
参数映射,每行 key=value |
query_code 两种形式
① frappe.qb 查询构建器(Python 代码) — 把结果赋给 result
代码在 safe_exec 沙箱里执行,暴露以下对象:qb · DocType · desc/asc · Sum/Count/Avg/Max/Min/Round/Concat/Coalesce/Abs/GROUP_CONCAT · filters(参数字典)。result 必须是 list(每行 dict) 或 dict。
# 示例:按物料查库存汇总(filters.item_code 由 parameters 注入)
bin = DocType("Bin")
result = (qb.from_(bin)
.select(bin.item_code, Sum(bin.actual_qty).as_("qty"))
.where(bin.item_code == filters.get("item_code"))
.groupby(bin.item_code)
.run(as_dict=True))
# 示例:查客户最近 5 笔销售(聚合 + 排序 + 限制)
si = DocType("Sales Invoice")
result = (qb.from_(si)
.select(si.name, si.grand_total, si.posting_date)
.where(si.customer == filters.get("customer"))
.orderby(si.posting_date, order=desc())
.limit(5)
.run(as_dict=True))
② 函数路径(app.module.file.function) — 调用项目里的 Python 函数,parameters 作为 kwargs 传入
query_code: zhiz_gy.api.report.get_sales_summary
函数返回 list[dict](每行一个字典) 或 list[list](自动转为 c0/c1/.. 列名)。适合复杂业务逻辑(多表 JOIN、Python 计算)封装在项目 app 里复用。
参数(parameters)
每行 key=value,value 支持三种:
company=广优 ← 字面量
company=doc.company ← 单据字段(打印时动态替换)
from_date={{doc.posting_date}} ← 双花括号写法(等价上一行)
item_code=doc.items.item_code ← 子表字段(数据驱动行展开时按行取)
参数注入到 filters 字典,在 query_code 里用 filters.get("key") 读取。
引用查询结果
单值(取首行) — {query_name.column}:
当前库存:{stock.qty}
本月汇总:{summary.total_amount}
数据驱动行(按查询结果展开) — 在单元格属性面板设 query_name + data_key,该行按查询结果条数自动展开,每行一条结果:
| cell 配置 | 说明 |
|---|---|
query_name = bom_detail |
绑定查询名 |
data_key = item_code |
每行取该列作为主键 |
cell_value {bom_detail.qty} |
自动按行匹配该行的 qty |
完整示例:打印销售订单的 BOM 子件清单
- design_queries 新增查询:
- queryname =
bom_detail - querycode(查 BOM 子件):
bom = DocType("BOM") bi = DocType("BOM Item") result = (qb.from_(bi) .select(bi.item_code, bi.qty, bi.stock_uom) .left_join(bom).on(bom.name == bi.parent) .where(bom.name == filters.get("bom_no")) .run(as_dict=True)) - parameters:
bom_no=doc.bom_no
- queryname =
- 数据驱动行 cell 设 queryname=
bom_detail、datakey=item_code - 单元格引用:
{bom_detail.item_code}/{bom_detail.qty}/{bom_detail.stock_uom}
> 💡 查询执行失败会记 Error Log 并返回空结果(单元格显示空),不会中断整个打印。
五、单元格值的四种语义 (核心)
这是 zhiz_print 最强大的能力:cell_value 不只是文本替换,而是根据写法自动选择四种运算语义之一。
① static — 直接替换(默认)
普通文本 + {占位符},逐一替换。无 = 前缀、cell_type 非 logic。
客户:{doc.customer}
金额:{doc.grand_total}
备注:{doc.items.additional_notes}
② logic — 条件表达式(Python) · cell_type = logic
把单元格类型设为 logic,cell_value 写 Python 表达式。用于跨表取值、条件判断。暴露变量与助手:
| 可用 | 说明 |
|---|---|
doc / row |
目标单据 / 当前子表行(数据驱动行展开时) |
get_value(doctype, name, field) |
跨表单字段查询(返回字符串,空时返回 '') |
fmt(value, precision=2) |
数字格式化(str.format 被 safe_eval 禁,必须用 fmt) |
flt · max · min · round |
数值与内置函数(safe_eval 默认禁内置,需显式暴露) |
get_value("Item", row.item_code, "classification") ← 取物料的"分类"字段
"滑板" if get_value("Item", row.item_code, "classification") == "滑板" else "其他"
fmt(row.qty * row.weight_per_unit, 3)
> ⚠️ 注意:safe_eval 禁用 str.format、frappe.utils.* 属性访问、内置函数。务必用上表的 fmt/flt/get_value,不要写 frappe.utils.flt(x)。
③ =expression — 算术表达式 (新)
cell_value 以 = 开头:先做占位符替换,再对整个表达式 safe_eval 求值。用于行内运算。
={doc.items.qty}*{doc.items.weight_per_unit} ← 总重 = 数量 × 件重
={doc.items.rate}*{doc.items.qty} ← 金额 = 单价 × 数量
=round({doc.items.weight_per_unit}, 4) ← 件重保留 4 位
求值结果用 _fmt_val 格式化(去尾零:50.0 → "50"、5.10 → "5.1")。字段为空导致表达式非法时(如 100*)记录错误日志并显示原式,便于发现缺数据。
④ =rowsum(R:C) — 合计函数 (新)
对第 R 行(数据驱动行)所有展开项的第 C 列显示值求和。用于合计行。兼容中英文括号 () / ()。
=rowsum(5:5) ← 第5行第5列(数量)合计
=rowsum(5:7) ← 第5行第7列(总重)合计 —— 第7列本身是 =round(...) 表达式也能正确累加
=rowsum(5:9) ← 第5行第9列(金额)合计
> 💡 关键:rowsum 复用列值计算逻辑,所以合计对象即使是 = 表达式列、logic 列,都能算对。非数字单元格自动跳过。
>
> 📊 报表打印(v15.22):报表引擎自动附加的合计行不会带入打印 —— 报表合计请用 =rowsum() 在模板里自行设计(如数据驱动行为第 8 行、数量在第 4 列,合计行单元格填 =rowsum(8:4);每页小计用 =pagerowsum(8:4))。
⑤ =pagerowsum(R:C) — 当前打印页合计函数 (新)
与 =rowsum(R:C) 用法完全相同(R=数据驱动行号、C=列号),区别只在合计范围:当数据驱动行的数据多到跨多张打印页时,=pagerowsum(R:C) 只合计当前这一张打印页上显示的数据,而 =rowsum(R:C) 合计当前逻辑页的全部数据。兼容中英文括号。
=pagerowsum(5:7) ← 第5行第7列,只合计本页显示的数据(本页小计)
=rowsum(5:7) ← 第5行第7列,合计全部数据(总计)
> 💡 用法:把 =pagerowsum(R:C) 单元格放在每页都重复出现的行(把该行的行类型设为 Repeat Title Row 重复标题行)里,这样每一张打印页渲染时它都会算一次,且用的是当前页的数据,正好得到「本页小计」;把 =rowsum(R:C) 放在数据行之后的普通行(只出现一次),用于「总计」。若 pagerowsum 放在普通行,它只在第一页出现、只算第一页的数据。
四种语义对照表
| 写法 | 触发条件 | 示例 | 结果 |
|---|---|---|---|
| static | 无 = 前缀,非 logic |
客户:{doc.customer} |
替换占位符 |
| logic | cell_type = logic | get_value("Item", row.item_code, "x") |
Python 表达式求值 |
| =expression | = 开头(非 rowsum) |
={doc.items.qty}*{doc.items.rate} |
替换后算术求值 |
| =rowsum | =rowsum(R:C) |
=rowsum(5:7) |
第R行第C列合计 |
| =pagerowsum | `=pag |
Related Other apps for Frappe & ERPNext
- Erpnext — Free and Open Source Enterprise Resource Planning (ERP)
- Helpdesk — Modern, Streamlined, Free and Open Source Customer Service Software
- Print Designer — Visual print designer for Frappe / ERPNext
- Ctr — CTR模型代码和学习笔记总结
- Whitelabel — Whitelabel ERPNext
- Fossunited — fossunited.org
- Helm — Helm Chart Repository for Frappe/ERPNext
- Frappe Attachments S3 — A frappe app to upload file attachments in doctypes to s3.