Files
ReceiptGenerator/docs/ARCHITECTURE.md
T

102 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 架构与扩展
应用采用“桌面界面 → 白名单 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/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,保持前缀、命名方式和字符替换一致。
记录列表通过 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 分辨率、编号重启、设置持久化及自动打开目录。实际打印机的可打印边距需在交付时试打确认。