☰
电子礼簿3.0:多事项独立密码与PDF/Excel导出打印实战
2026/10/10 6:30:56 网站建设 项目流程

简介:这是一款面向红白喜事办宴场景的电子礼簿记账工具,适合需要管理婚礼、满月宴、寿宴、白事等事项的普通用户与帮亲友打理事务的人。它支持创建多个独立事项并分别设置管理密码,记录礼金时可录入姓名、金额、支付方式与备注,自动统计总人数与总金额,并提供搜索、筛选、排序、竖排礼单展示、字体颜色背景自定义,以及Excel与PDF导出打印。增强功能包括语音播报与起报门槛、内联修改留痕、分批打印带封面统计、Excel批量导入导出、加密备份恢复和统计分析。资源包共2000个文件,以js、md、json为主,另有少量py、html、xml、yaml等,压缩包约609.58MB,属于Electron桌面应用源码与依赖的完整工程。已有470人学习下载,可帮助读者快速获得一套可直接运行、可二次开发的礼簿管理方案,省去从零搭建的麻烦。

1. 电子礼簿 3.0:多事项独立密码与 PDF/Excel 导出打印,到底解决谁的痛点

村里办一场婚礼,收礼台三个人轮班,一个记名字、一个点钱、一个盯烟酒,散场后账本对不上,红纸写错一个字就得整页重抄。更麻烦的是,同一家人这个月办婚礼、下个月办满月宴,两本礼簿混在一起,谁随了多少、哪笔是婚礼的、哪笔是满月的,翻账翻到半夜。电子礼簿 3.0 这类工具瞄准的就是这个场景:把红白喜事的收礼记录从纸面搬到本地软件里,支持创建多个事项(婚礼、满月宴、寿宴、白事各算一个独立事项),每个事项可以单独设置管理密码,最后导出 PDF 或 Excel 打印成册。它适合的人群很明确——乡镇流动酒席的管事、家族里负责记账的“账房先生”、婚庆公司跟场人员,以及需要长期保存礼金往来记录的家庭。核心诉求就三条:记得快、查得到、印得出。这一章先把“多事项 + 独立密码 + 导出打印”这条链路讲清楚,后面几章再拆具体怎么落地、参数怎么设、哪里容易翻车。

2. 多事项与独立密码:数据模型怎么设计才不串账

2.1 为什么不能把所有礼金塞进一张表

很多人第一反应是“不就是记个账吗,一张 Excel 表够了”。真到用的时候才发现,婚礼和满月宴的礼金逻辑不一样:婚礼可能按男方女方分桌收,满月宴可能按亲戚朋友分组,白事又讲究“折仪”和“实物”分开记。如果全塞进一张表,字段会膨胀到没法看,查询时还得靠“事项名称”这一列去过滤,手一抖就串账。电子礼簿 3.0 的做法是每个事项独立一份数据文件或独立一张表,事项之间用唯一 ID 隔离。常见做法是 SQLite 单文件数据库,每个事项对应一个event_id,礼金记录表里带event_id外键。这样导出时按event_id筛选,打印出来的礼簿不会混入其他事项的记录。

独立密码的意义也在这里:婚礼事项的密码只有婚礼记账人知道,满月宴的密码给另一拨人,互相看不到对方的数据。密码不是用来加密整个软件的,而是用来解锁某个事项的管理权限。实现上通常是对每个事项存一个密码哈希(比如 bcrypt 或 PBKDF2),验证通过后才允许增删改查该事项的记录。注意不要用明文存密码,也不要用简单的 MD5,血泪经验是 MD5 撞库太容易,乡镇场景虽然风险低,但习惯要养好。

2.2 建库建表的最小 SQL 与参数说明

下面这段 SQL 是 SQLite 下建多事项礼簿的最小结构,可以直接抄。字段名用拼音或英文都行,关键是event_id要建索引,否则记录多了查询会慢。

-- 事项表:每个婚礼、满月宴、白事各一行 CREATE TABLE events ( event_id INTEGER PRIMARY KEY AUTOINCREMENT, event_name TEXT NOT NULL, -- 事项名称,如“2025年正月初六婚礼” event_type TEXT NOT NULL DEFAULT '婚礼', -- 婚礼/满月宴/寿宴/白事 event_date TEXT, -- 事项日期,存 ISO 格式 yyyy-MM-dd password_hash TEXT NOT NULL, -- 管理密码哈希,禁止明文 created_at TEXT DEFAULT (datetime('now','localtime')) ); -- 礼金记录表:每条随礼记录一行 CREATE TABLE gifts ( gift_id INTEGER PRIMARY KEY AUTOINCREMENT, event_id INTEGER NOT NULL, -- 外键,关联 events guest_name TEXT NOT NULL, -- 随礼人姓名 amount REAL DEFAULT 0, -- 礼金金额,单位元 gift_type TEXT DEFAULT '现金', -- 现金/实物/转账 relation TEXT, -- 关系,如“男方亲戚”“同事” table_no TEXT, -- 桌号,方便现场找座 remark TEXT, -- 备注,如“代随”“未到” created_at TEXT DEFAULT (datetime('now','localtime')), FOREIGN KEY (event_id) REFERENCES events(event_id) ); -- 关键索引:按事项查记录、按姓名查重 CREATE INDEX idx_gifts_event ON gifts(event_id); CREATE INDEX idx_gifts_name ON gifts(event_id, guest_name);

逻辑说明:events表存事项元信息和密码哈希,gifts表存每一笔礼金。event_id是隔离边界,所有查询都必须带WHERE event_id = ?。参数上,amount用 REAL 存金额,乡镇场景金额一般到元,REAL 够用;如果涉及分,建议改成 INTEGER 存“分”。password_hash字段长度至少 60,因为 bcrypt 输出是 60 字符。created_at用本地时间,方便打印时显示记录时间。索引idx_gifts_event是必须的,否则一个事项几百条记录时翻页会卡。idx_gifts_name用于现场快速查“这个人是不是已经随过了”,避免重复记账。

2.3 独立密码的校验流程与代码示例

密码校验不要自己发明加密算法,直接用成熟库。Python 下推荐bcrypt,Node.js 下推荐bcryptjs。下面以 Python 为例,展示创建事项时设密码、打开事项时验密码的最小逻辑。

import bcrypt import sqlite3 def create_event(conn, name, event_type, event_date, plain_password): """创建事项,密码只存哈希""" # 生成盐并哈希,cost 默认 12,乡镇机器性能一般可降到 10 pw_hash = bcrypt.hashpw(plain_password.encode('utf-8'), bcrypt.gensalt(rounds=10)) cur = conn.cursor() cur.execute( "INSERT INTO events (event_name, event_type, event_date, password_hash) VALUES (?,?,?,?)", (name, event_type, event_date, pw_hash.decode('utf-8')) ) conn.commit() return cur.lastrowid def open_event(conn, event_id, plain_password): """验证密码,通过则返回事项信息,否则返回 None""" cur = conn.cursor() cur.execute("SELECT event_name, password_hash FROM events WHERE event_id=?", (event_id,)) row = cur.fetchone() if not row: return None stored_hash = row[1].encode('utf-8') if bcrypt.checkpw(plain_password.encode('utf-8'), stored_hash): return {"event_id": event_id, "event_name": row[0]} return None

逻辑说明:create_event把明文密码用 bcrypt 哈希后存库,数据库里永远看不到明文。open_event取出哈希后用checkpw比对,比对成功才返回事项信息。参数上,rounds=10是计算强度,数值越大越安全但越慢,乡镇老电脑建议 10,新机器可以 12。注意bcrypt库需要单独安装,pip install bcrypt。如果不想装第三方库,Python 标准库的hashlib.pbkdf2_hmac也能用,但盐要自己管理,容易出错,新手还是用 bcrypt 省心。常见翻车点是密码里带中文或特殊字符,编码不统一导致校验失败,统一用 UTF-8 编码就能避开。

3. 从录入到导出:PDF 与 Excel 两条打印链路的落地步骤

3.1 现场录入的字段设计与快速查重

录入界面不用花哨,但字段顺序要符合收礼台的实际动线:先问姓名,再点金额,然后选关系、填桌号,最后备注。电子礼簿 3.0 支持多事项后,录入前必须先选事项并输入该事项的密码,避免记到别人事项里。快速查重是现场刚需,常见做法是输入姓名时实时查gifts表,如果该事项下已有同名记录,弹出提示“张三已随礼 200 元,是否继续”。SQL 就是前面建的idx_gifts_name索引对应的查询:

SELECT guest_name, amount, created_at FROM gifts WHERE event_id = ? AND guest_name = ? ORDER BY created_at DESC LIMIT 1;

参数说明:event_id是当前事项,guest_name是输入框里的姓名。注意姓名可能有重名,所以提示时要把金额和时间一起显示,让记账人自己判断是不是同一个人。如果同名但金额不同,可能是两个人,也可能是重复录入,现场问一句最稳妥。这个查询走索引,几百条记录毫秒级返回,不会卡输入。

3.2 导出 Excel:用 openpyxl 生成可打印的礼簿

Excel 导出适合需要二次编辑的场景,比如事后按关系分类统计、按桌号排序。Python 下用openpyxl最直接,下面代码把某个事项的礼金记录导出成带表头的 xlsx,并设置列宽和打印区域。

from openpyxl import Workbook from openpyxl.styles import Font, Alignment import sqlite3 def export_event_to_excel(conn, event_id, out_path): cur = conn.cursor() cur.execute("SELECT event_name FROM events WHERE event_id=?", (event_id,)) event_name = cur.fetchone()[0] cur.execute(""" SELECT guest_name, amount, gift_type, relation, table_no, remark, created_at FROM gifts WHERE event_id=? ORDER BY table_no, guest_name """, (event_id,)) rows = cur.fetchall() wb = Workbook() ws = wb.active ws.title = "礼金明细" # 表头 headers = ["姓名", "金额", "类型", "关系", "桌号", "备注", "记录时间"] ws.append(headers) for cell in ws[1]: cell.font = Font(bold=True) cell.alignment = Alignment(horizontal="center") # 数据行 for r in rows: ws.append(list(r)) # 列宽,姓名和备注宽一点 widths = [12, 10, 8, 14, 8, 20, 20] for i, w in enumerate(widths, start=1): ws.column_dimensions[chr(64+i)].width = w # 打印设置:A4 纵向,适应一页宽 ws.page_setup.orientation = "portrait" ws.page_setup.fitToWidth = 1 ws.page_setup.fitToHeight = 0 ws.print_title_rows = "1:1" # 每页重复表头 wb.save(out_path) return event_name, len(rows)

逻辑说明:先查事项名称,再按桌号和姓名排序查记录,这样打印出来同一桌的人挨在一起,现场核对方便。表头加粗居中,列宽按内容调整,备注列给 20 字符宽,避免长备注被截断。打印设置里fitToWidth=1保证所有列缩到一页宽,print_title_rows="1:1"让每页都重复表头,翻页时不会不知道哪列是什么。参数上,out_path建议用“事项名称_日期.xlsx”命名,方便归档。注意openpyxl写大文件时内存占用较高,一个事项几千条记录没问题,上万条建议分 Sheet 或改用pandas的to_excel。常见翻车点是金额列被 Excel 识别成文本,导出后不能求和,解决办法是写入时保持数值类型,不要转成字符串。

3.3 导出 PDF:用 reportlab 生成可直接打印的礼簿

PDF 适合直接打印成册,不需要收礼人再编辑。Python 下用reportlab可以精确控制版式,下面代码生成一个带标题、表头和分页的 PDF。

from reportlab.lib.pagesizes import A4 from reportlab.lib import colors from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer from reportlab.lib.styles import getSampleStyleSheet from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.cidfonts import UnicodeCIDFont import sqlite3 # 注册中文字体,否则中文显示为方块 pdfmetrics.registerFont(UnicodeCIDFont('STSong-Light')) def export_event_to_pdf(conn, event_id, out_path): cur = conn.cursor() cur.execute("SELECT event_name, event_date FROM events WHERE event_id=?", (event_id,)) event_name, event_date = cur.fetchone() cur.execute(""" SELECT guest_name, amount, relation, table_no, remark FROM gifts WHERE event_id=? ORDER BY table_no, guest_name """, (event_id,)) rows = cur.fetchall() doc = SimpleDocTemplate(out_path, pagesize=A4, leftMargin=36, rightMargin=36, topMargin=36, bottomMargin=36) styles = getSampleStyleSheet() styles['Title'].fontName = 'STSong-Light' styles['Normal'].fontName = 'STSong-Light' story = [] story.append(Paragraph(f"{event_name} 礼金簿", styles['Title'])) story.append(Paragraph(f"日期:{event_date or ''}", styles['Normal'])) story.append(Spacer(1, 12)) data = [["姓名", "金额", "关系", "桌号", "备注"]] for r in rows: data.append([str(x) if x is not None else "" for x in r]) table = Table(data, colWidths=[80, 60, 80, 50, 150]) table.setStyle(TableStyle([ ('FONTNAME', (0,0), (-1,-1), 'STSong-Light'), ('FONTSIZE', (0,0), (-1,-1), 10), ('BACKGROUND', (0,0), (-1,0), colors.lightgrey), ('GRID', (0,0), (-1,-1), 0.5, colors.grey), ('ALIGN', (1,1), (1,-1), 'RIGHT'), ('VALIGN', (0,0), (-1,-1), 'MIDDLE'), ])) story.append(table) doc.build(story) return event_name, len(rows)

逻辑说明:reportlab默认不支持中文,必须注册UnicodeCIDFont('STSong-Light'),否则打印出来全是方块,这是最常见的翻车点。表格列宽按 A4 可用宽度分配,备注列给 150 磅,长备注会自动换行。GRID画网格线,打印后手写补录也方便。参数上,页边距 36 磅约 1.27 厘米,够窄但不至于打印不全。如果事项记录超过一页,SimpleDocTemplate会自动分页,表头不会重复,需要重复表头的话得用LongTable并设置repeatRows=1。注意STSong-Light是内置 CID 字体,不需要额外字体文件,但显示效果偏宋体,想要黑体可以换STHeiti-Light。导出 PDF 前建议先导出 Excel 核对一遍数据,避免把错账印成册。

4. 避坑与排查:多事项礼簿最容易翻车的 5 个地方

4.1 事项密码忘了,数据打不开

现象:婚礼事项设了密码,过了半年想导出,密码怎么试都不对,事项打不开。原因:密码哈希不可逆,没有找回机制,这是设计上的安全取舍。解决:建事项时强制填一个“密码提示”字段,或者把密码写在纸质礼簿封面内侧。如果已经忘了,只能直接改数据库:用 SQLite 工具打开库文件,把events表里对应行的password_hash更新为一个新密码的哈希值。新哈希可以用 Python 跑bcrypt.hashpw(b'新密码', bcrypt.gensalt())生成。注意改之前先备份库文件,改错了还能恢复。

4.2 导出 Excel 后金额不能求和

现象:Excel 里金额列左上角有绿色小三角,求和结果是 0。原因:写入时金额被当成字符串,或者单元格格式是文本。解决:openpyxl写入时确保amount是float或int,不要str(amount)。如果已经导出,选中金额列,用“分列”功能直接完成,或者把单元格格式改成“数值”。更稳妥的做法是在导出代码里显式设置cell.number_format = '0.00',这样打开就是数值格式。

4.3 PDF 中文显示成方块

现象:导出的 PDF 里姓名和事项名称全是方块或问号。原因:reportlab默认字体不支持中文,没有注册中文字体。解决:在doc.build之前注册UnicodeCIDFont('STSong-Light'),并在所有Paragraph和TableStyle里指定fontName='STSong-Light'。如果用了自定义 TTF 字体,要确保字体文件路径正确,并且pdfmetrics.registerFont(TTFont('自定义名', '字体路径.ttf'))注册后再用。注意 CID 字体不需要字体文件,但只支持部分中文字体名,写错名字一样会翻车。

4.4 多事项查询忘了带 event_id 条件

现象:打开婚礼事项,列表里却混进了满月宴的记录。原因:查询 SQL 写成了SELECT * FROM gifts,漏了WHERE event_id=?。解决:所有涉及gifts表的查询都必须带event_id条件,建议在代码里封装一个get_gifts_by_event(event_id)函数,禁止直接裸查。数据库层面可以加视图或触发器,但最简单的是代码规范加 Code Review。血泪经验是现场忙起来容易复制粘贴旧代码,漏条件这种事发生过不止一次。

4.5 打印时每页不重复表头

现象:礼簿打印出来第一页有表头,第二页开始只有数据,翻页后不知道哪列是金额。原因:PDF 导出用了普通Table,没有设置重复表头;Excel 导出没有设置print_title_rows。解决:PDF 改用LongTable并传repeatRows=1;Excel 设置ws.print_title_rows = "1:1"。另外打印前用打印预览确认一下,A4 纵向能不能放下所有列,放不下就调整列宽或改成横向。注意fitToWidth=1和repeatRows要同时设,只设一个可能还是出问题。

5. 进阶技巧:用模板批量导出多个事项并自动归档

多事项用久了,年底想一次性把所有事项的礼簿都导出来归档,一个个点太慢。我一般会写一个批量脚本,遍历events表,对每个事项分别导出 Excel 和 PDF,按“事项名称_类型_日期”命名放到对应文件夹。下面这段代码是批量导出的骨架,复用了前面两个导出函数。

import os import sqlite3 from export_excel import export_event_to_excel from export_pdf import export_event_to_pdf def batch_export(db_path, out_dir): conn = sqlite3.connect(db_path) cur = conn.cursor() cur.execute("SELECT event_id, event_name, event_type, event_date FROM events ORDER BY event_date") events = cur.fetchall() for event_id, name, etype, edate in events: # 文件夹名去掉非法字符 safe_name = "".join(c for c in name if c not in r'\/:*?"<>|') folder = os.path.join(out_dir, f"{safe_name}_{etype}_{edate or '无日期'}") os.makedirs(folder, exist_ok=True) excel_path = os.path.join(folder, f"{safe_name}.xlsx") pdf_path = os.path.join(folder, f"{safe_name}.pdf") export_event_to_excel(conn, event_id, excel_path) export_event_to_pdf(conn, event_id, pdf_path) print(f"已导出:{folder}") conn.close() if __name__ == "__main__": batch_export("libu.db", "./归档")

逻辑说明:先查所有事项,按日期排序,然后为每个事项建一个文件夹,文件夹名里去掉 Windows 不允许的字符,避免创建失败。分别调用 Excel 和 PDF 导出函数,文件名用事项名称。参数上,db_path是 SQLite 库文件路径,out_dir是归档根目录。注意批量导出时如果某个事项密码保护,这里直接读库不需要密码,因为密码只控制界面操作,不加密数据文件。如果希望数据文件也加密,得用 SQLCipher 之类的方案,那是另一个话题了。跑完脚本后,归档目录里每个事项一个文件夹,里面 Excel 和 PDF 各一份,年底打包发给家族长辈或者存网盘都方便。

验证导出结果是否完整,我习惯用三个检查:一是 Excel 里记录条数和界面显示条数一致;二是 PDF 打印预览翻到最后一页,看总条数对不对;三是随机抽三条记录,核对姓名、金额、桌号有没有错位。这三个检查花不了两分钟,但能避免把错账印成册的尴尬。最后说个习惯:每次办完事,当天就把礼簿导出 PDF 和 Excel 各存一份,库文件也备份一份到 U 盘,别等半年后想不起来密码才后悔。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询