Files
ReceiptGenerator/docs/ARCHITECTURE.md
T
2026-10-19 16:47:24 +08:00

12 KiB
Raw Blame History

架构与扩展

应用采用“桌面界面 → 白名单 IPC → 应用服务 → 业务规则与存储”的结构。Renderer 不直接访问文件系统或数据库;主进程不接受界面传来的任意 HTML、SQL 或脚本。

模块边界

位置 职责
src/shared 接口类型、设置契约、输入校验;不依赖 Electron
src/shared/receipt-groups.ts 共用分组层级、分组摘要、勾选统计及 A4 预览选页
src/shared/file-names.ts 文件名规则、字符处理;设置示例和正式导出共用
src/core/money.ts 整数分、金额上限、人民币大写、金额格
src/core/date.ts 日期与 Excel 日期制
src/core/templates.ts 模板接口、注册器、打印页面组合;与桌面环境解耦
src/core/preview-samples.ts 未导入数据时的示例收据,按当前页面容量生成,不分配流水号
src/main/workbooks.ts WorkbookReader 接口及 .xlsx 适配器
src/main/ledger.ts 持久编号、批次快照及删除记录
src/main/settings-store.ts 版本化设置及原子写入
src/main/confirmations.ts 主进程确认请求、回复匹配与取消
src/renderer/components/ConfirmDialog.vue 基于 Reka UI 的统一确认弹窗
src/renderer/components/ActionButton.vue 操作触发、禁用及行内目标匹配
src/renderer/components/LoadingRegion.vue 整块区域遮罩、转圈、区域错误及取消入口
src/renderer/components/ReceiptGroupTree.vue 可折叠的整组勾选、部分选中状态及按需显示
src/renderer/loading.ts 操作任务与板块加载状态映射
src/renderer/confirmations.ts 界面确认状态及焦点恢复
src/main/seals.ts 本地图片校验、缓存、快照
src/main/pdf-engine.ts PdfEngine 接口及 Electron 渲染适配器
src/main/print-documents.ts 内存打印页面、随机本地地址及内容释放
src/main/exporter.ts 导出编排、批量分块、取消、清单与进度
src/main/export-layout.ts 学校/年级/收费项目分组、目录名称及冲突处理
src/main/index.ts 桌面生命周期、原生对话框、IPC 接线
src/renderer Vue 页面、交互状态、收据预览及 PDF 转 PNG

界面划分为工作台、印章、记录、设置。收据 HTML 不直接从 Vue 界面截图,预览与导出都使用同一模板注册器,防止两套排版逐渐分离。

操作反馈

操作反馈位于对应板块。LoadingRegion 通过 as 保留原表单、面板和网格结构,显示整块浅色遮罩与居中转圈。指示器按板块与视口的交集定位,长页面滚动后仍可见。加载时 region-content 使用 inert 阻止鼠标与键盘操作,完成后恢复内容;页面顶部不再显示独立成功或错误提示条。

usePanelState 将设置、印章、生成记录、导入数据、预览与导出六个板块映射到任务。actions.task 在启动时绑定所属板块,防止同类操作重入;操作立即执行,保存和手动刷新等反馈从首帧绘制后计时,至少可见 500 毫秒;较慢的操作显示到真实完成。loadingTasks 与 busy 分开:选择/打开文件夹、切换印章及后台同步不会产生人为等待或整块遮罩。主进程通过 onProcessing 在导入、模板保存和删除的选择/确认步骤结束后通知界面开始转圈,取消选择直接结束。错误写入 panelErrors 并显示在对应区域,可关闭;启动错误显示在启动区域。

ActionButton 负责事件和禁用状态,图标与文字保持稳定。导出遮罩使用 loading-actions 插槽提供取消按钮,该插槽位于 inert 内容之外。取消请求发出后显示“正在取消”,直至主进程报告结束。预览版本号保证旧请求不能关闭新请求的加载反馈。必要的进度转圈使用独立的 region-spinner 动画,在 prefers-reduced-motion 下仍旋转;其余装饰过渡遵循系统偏好。scripts/loading-test.mjs 在可见的测试窗口逐帧检查旋转角度和持续时间,使用隔离配置及文件夹验证选择器、取消和处理阶段。

确认弹窗

使用 Reka UI AlertDialog,并封装统一的标题、说明、详情、提示与按钮。弹窗提供焦点约束、默认聚焦取消和 Esc 取消,业务内容通过 Vue 文本绑定呈现。删除、重复生成和退出确认共用同一组件;离开未保存设置使用同一界面服务。

主进程通过 ConfirmationService 发送带随机 id 的请求,等待白名单 IPC 回复。仅匹配当前请求的回复有效,同时只允许一个主进程请求。页面导航、进程异常或窗口关闭会取消等待中的确认。操作执行仍保留主进程的数据校验与任务状态检查。新增主进程确认调用 confirmations.ask,新增纯界面确认调用 askConfirmation。

Reka UI 按需打包到本地前端,运行不依赖网络;许可声明随构建输出到 renderer/licenses/reka-ui.txt。

新增收据模板

实现 ReceiptTemplate 的 id、name、css、render,并在 TemplateRegistry 构造时注册。id 是持久标识,发布后保持稳定;已有模板发生版式不兼容变化时使用新 id,保留旧实现以支持历史重打。设置页自动从注册器读取可用模板。

模板使用物理毫米布局,单张页面与 A4 页面由 buildDocument 统一组合。render 中所有业务文字经过 escapeHtml,图片仅接受已校验本地文件转换的数据 URL。

新增导入来源

实现 WorkbookReader.read,将新来源映射为 ImportDraft、ImportRow 和 ReceiptInput。收据编号由 Ledger 分配,导入适配器不创建编号。导入层保留 school、grade、studentName 三个来源字段,并生成以单个空格分隔的 customer 展示文字。来源字段对旧记录为可选,历史客户名称继续按快照读取,避免升级后影响补打。CSV、旧 .xls 或其他来源都可以增加适配器,再在入口按扩展名选择。

新增导出格式

ExportCoordinator 只依赖 PdfEngine 接口。可以替换渲染器而不改变编号与导入。新增格式在 ExportOptions 中明确声明,输入经共享校验器校验;禁止将任意文件路径和脚本通过 IPC 暴露给网页。

打印 HTML 通过随机的 receipt://print/ 地址在内存中提供,避免把整页及重复印章编码进地址而触发长度限制。每次 PDF 导出完成或失败后释放页面内容;不写临时 HTML 文件。错误提示与历史摘要会省略内嵌页面内容并限制长度。

PNG 使用已生成 PDF 的第一页渲染,保持字体和版式一致。A4 批量打印每次最多渲染 40 张收据再合并 PDF;分块大小为偶数,以免每个分块引入额外空白半页。

导出目录保留现有批次外层,内部由 groupReceipts 按 school、grade、item 三个来源字段逐层规划。目录层级定义集中在 shared/receipt-groups.ts 的 receiptGroupLevels 中,导出路径、界面核对和历史分类共用这一规则。缺失的历史字段使用未分类目录,不拆解或修改旧客户名称。目录名过滤 Windows 非法字符与保留名,截断或替换后的名称加稳定摘要,并处理大小写冲突,避免不同分组合并。

单张 PDF/PNG 按原收据顺序写入所属目录,每组 A4 单独生成,组内仍按原顺序每 40 张分块。单组失败后继续其余分组,仅打印时按分组累计成功/失败张数。生成清单保留在批次根目录,receipts.directory、receipts.files 和 printFiles 使用相对路径;重新导出从原快照重新规划目录,沿用原编号并保留同名文件追加后缀的行为。

分组核对与历史查找

importGroupTree 按三级分组统计有效行、待修改行、选中张数和金额;选择学校或年级时只切换有效行。界面使用原生 details 和复选框,支持部分选中状态;多于 20 个有效分组时减少初始展开,同级每次显示 50 项,关闭节点的子树按需创建。勾选直接更新原有 selected 集合。

A4 预览使用 printPreviewRows 在已选同组收据中定位当前行所在页,并按全局已选顺序展示待分配编号。预览查询不修改流水号。设置页面的实时文件名示例和正式导出使用相同的 receiptFileStem,保持前缀、命名方式和字符替换一致。

工作台预览请求显式传递当前 receiptsPerPage,主进程校验为 1 或 2,并只覆盖本次预览上下文;省略时沿用已保存设置,历史预览继续使用原批次快照。切换每页张数无需先保存设置。未导入时由 createPreviewSamples 提供对应张数的不同示例;导入后始终使用真实勾选数据,不补充示例。界面共用 printPreviewRows 计算本页张数,并展示当前分组、组内已选数量及空白半页原因。

记录列表通过 listBatchPage 传递经校验的学校、年级、项目、文本、页码和每页条数,返回当前页、真实总条数及有效页码。无筛选时使用数据库 COUNT/LIMIT/OFFSET;有筛选时逐批匹配同一张收据,统计所有匹配批次并仅构建当前页摘要,末页删除后自动钳制到有效页。学校、姓名和任意编号查找仍覆盖全库。原 listBatches 继续为已有调用提供最多 200 条摘要,bootstrap 提供全库分类选项和真实总数。

界面默认每页 5 条,可选 5/10/20/50;historyPageSize 在设置校验器中为旧配置补默认值,修改后持久保存。筛选或每页条数变化时回到第一页,刷新保留当前页;使用请求版本号保持最新结果。显式翻页沿用整块加载反馈,自动筛选合并短时间内的输入变化。过滤结果仍对应完整批次,重新导出维持原有批次语义。原收据预览可指定匹配编号,旧批次输出目录经过全库归属检查。

编号一致性

应用使用单实例锁;主进程生成任务互斥。Ledger 内部串行事务分配流水,先原子持久保存数据库,再写导出文件。编号不因取消或失败而回收。原批次重试不再次 reserve。

批次保存原数据、默认姓名生效后的内容、模板 id、设置及印章图片快照。更换印章或移动图片不改变历史批次。导出位置与导出选项可以变化,正式编号和内容保持原值。

批次记录支持删除。主进程在确认前后检查生成任务状态,Ledger 在事务中拒绝删除 reserved/running 批次,仅移除批次快照,保留 receipt_numbers 和 sequences。已导出的文件不会由此功能删除。删除后清理当前草稿对该批次的引用。

数据损坏时停止启动和分配编号,保留原文件,不静默创建空数据库。

设置与升级

设置包含 schemaVersion。增加可选字段时为共享校验器和 defaultSettings 同时补默认值,保证旧配置可加载。更改数据库结构需在 Ledger.open 中增加显式事务迁移,再写兼容性测试。不要通过删除用户数据解决升级问题。

设置页面保留文件、打印、印章和编号四个板块,单张文件命名方式直接显示在文件板块。独立批次目录与成功后打开目录为固定启用行为,无界面开关;SettingsStore 在读取和保存时将旧配置中的 createBatchFolder、openAfterExport 归一为 true,其余偏好保持原值。Exporter 每次都创建批次目录,包括从旧快照重新导出。备份和恢复的界面、IPC、预加载接口、状态操作及数据库合并代码已移除。

验证

核心测试覆盖金额边界、日期、工作簿错误、模板转义、持久编号、旧配置兼容、分块排版、取消和重试。桌面测试实际点击界面并检查 PDF 页数、A4 尺寸、PNG 分辨率、编号重启、设置持久化及自动打开目录。实际打印机的可打印边距需在交付时试打确认。