@agile-team/mach-report 1.1.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +316 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="https://raw.githubusercontent.com/ChenyCHENYU/MachReport/main/assets/mach-report-logo.svg" alt="MachReport" width="760" />
|
|
4
|
+
|
|
5
|
+
# @agile-team/mach-report
|
|
6
|
+
|
|
7
|
+
**面向 B 端打印报表的高性能 TypeScript 引擎**:模板 → RenderPlan 单真相源 → 屏幕 / PDF / Excel / 打印四出口,
|
|
8
|
+
契约级兼容 jh4j-cloud-report——换前端渲染层,后端零改动。
|
|
9
|
+
|
|
10
|
+
[](https://www.npmjs.com/package/@agile-team/mach-report)
|
|
11
|
+
[](https://github.com/ChenyCHENYU/MachReport/actions/workflows/ci.yml)
|
|
12
|
+
[](https://www.npmjs.com/package/@agile-team/mach-report)
|
|
13
|
+
[](https://www.npmjs.com/package/@agile-team/mach-report)
|
|
14
|
+
[](#-license)
|
|
15
|
+
|
|
16
|
+
[快速开始](#快速开始) · [核心能力](#核心能力) · [配置中心](#配置中心) · [引擎 API](#引擎-api) · [从 jh4j 迁移](#从-jh4j-迁移)
|
|
17
|
+
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 快速开始
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pnpm add @agile-team/mach-report # 唯一依赖:vue 为可选 peer,重能力全部子路径按需
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
// main.ts —— 入口一次注册,业务页面从此零胶水代码
|
|
30
|
+
import { machReportPlugin } from "@agile-team/mach-report/vue";
|
|
31
|
+
import "@agile-team/mach-report/vue/style.css";
|
|
32
|
+
|
|
33
|
+
app.use(machReportPlugin); // 零配置:同源直连 jh4j 端点
|
|
34
|
+
// 或 app.use(machReportPlugin, { baseUrl: "/sub/mach-report", request: axios });
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```vue
|
|
38
|
+
<!-- 任意业务页面:一行组件。打印 / PDF / Excel / 搜索 / 缩略图 / 参数面板全部内置 -->
|
|
39
|
+
<MachReportPreview temp-id="CK_TEMPLATE_001" :params="{ id: '9' }" height="calc(100vh - 206px)" />
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## 核心能力
|
|
43
|
+
|
|
44
|
+
| 域 | 能力 |
|
|
45
|
+
|---|---|
|
|
46
|
+
| **报表语义** | 分组小计 / 总合计(防孤行)、页码占位符 `{page}/{totalPages}`、列格式化(千分位 / 日期 / 百分比)、条件格式规则 |
|
|
47
|
+
| **模板体系** | DSL / JSON / jh4j 存量导入三来源;多模板拼接;公共页眉页脚;参数面板(声明式查询条件) |
|
|
48
|
+
| **渲染性能** | 5k 行分页 ~57ms;页级虚拟化;transform 缩放零重排;Canvas 窗口化分页器(内存 O(窗口)) |
|
|
49
|
+
| **导出出口** | PDF 前端矢量直出(字体子集化 + 缓存)、Excel 真表格、**Word 可编辑 .doc**、**图片 PNG/JPEG**(每页一张)、打印(named pages / 流式分块)——全部零后端 |
|
|
50
|
+
| **预览交互** | 搜索(跳页 + 高亮)、Ctrl+滚轮 / 键盘缩放翻页、缩略图侧栏、缩放记忆、调试面板 `?mrp-debug=1` |
|
|
51
|
+
| **工程健壮性** | 加载竞态代际令牌、结构化错误码、请求超时、批量打印合并文档、渲染计划规模告警 |
|
|
52
|
+
|
|
53
|
+
<details>
|
|
54
|
+
<summary><b>单包架构说明(为什么只有一个包)</b></summary>
|
|
55
|
+
|
|
56
|
+
框架组件在 `./vue` 子路径(vue 声明为 **optional peer**——React / Node 宿主不会被装上 vue);
|
|
57
|
+
pdf-lib / node-sql-parser / exceljs 分别内联进 `./pdf` `./sql` `./xlsx` 子路径产物——**不导入不进依赖图**,
|
|
58
|
+
主入口零框架耦合零重依赖(ESLint 边界规则 + 产物守卫测试双保险)。
|
|
59
|
+
自引用(self-reference)让 `./vue` 静态复用引擎、PDF 懒加载走 `./pdf`,运行时引擎代码全局单份。
|
|
60
|
+
ESM + CJS 双格式 + `.d.ts/.d.cts` 双声明,Node ≥ 18。
|
|
61
|
+
|
|
62
|
+
</details>
|
|
63
|
+
|
|
64
|
+
<details>
|
|
65
|
+
<summary><b>三种集成姿势(全局插件 / 局部导入 / 异步入口)</b></summary>
|
|
66
|
+
|
|
67
|
+
**① 全局插件(推荐)**:上文快速开始即是。插件注册全局组件 `<MachReportPreview>`(懒加载分片零首屏成本)、注入数据面 / PDF 导出器 / 配置中心。
|
|
68
|
+
|
|
69
|
+
**② 局部导入**——单页面使用:
|
|
70
|
+
|
|
71
|
+
```vue
|
|
72
|
+
<script setup lang="ts">
|
|
73
|
+
import { ReportPreview, createLocalFetcher } from "@agile-team/mach-report/vue";
|
|
74
|
+
const fetcher = createLocalFetcher({
|
|
75
|
+
T1: { tempId: "T1", template, datasets: { detail: rows } }
|
|
76
|
+
});
|
|
77
|
+
</script>
|
|
78
|
+
<template>
|
|
79
|
+
<ReportPreview temp-id="T1" :fetcher="fetcher" @loaded="onLoaded" @error="onError" />
|
|
80
|
+
</template>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**③ 异步入口**——首屏敏感页面(组件拆独立分片 + 预取):
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import AsyncMachReportPlugin, { preloadMachReport } from "@agile-team/mach-report/vue/async";
|
|
87
|
+
app.use(AsyncMachReportPlugin, { baseUrl: "/sub/mach-report" });
|
|
88
|
+
void preloadMachReport(); // 路由 hover 时预取,正式渲染零等待
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
</details>
|
|
92
|
+
|
|
93
|
+
<details>
|
|
94
|
+
<summary><b>配置中心(presets / 文案 / 主题 / 日志通道)</b></summary>
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
// src/config/mach-report.config.ts —— 集中到独立配置文件
|
|
98
|
+
import { defineMachReportConfig, defineMachReportPreset } from "@agile-team/mach-report/vue";
|
|
99
|
+
|
|
100
|
+
export default defineMachReportConfig({
|
|
101
|
+
defaults: {
|
|
102
|
+
gapPx: 18,
|
|
103
|
+
pdfFontUrl: "/simhei.ttf",
|
|
104
|
+
showParams: true, // 参数面板
|
|
105
|
+
messages: { title: "Report Preview" }, // 全量文案可覆写(i18n)
|
|
106
|
+
theme: { shellBg: "#2b2b2b", btnActiveBg: "#1677ff" }, // --mrp-* 主题变量
|
|
107
|
+
logger: console // 校验告警通道(静默传 SILENT_LOGGER)
|
|
108
|
+
},
|
|
109
|
+
presets: {
|
|
110
|
+
lean: defineMachReportPreset({ showExport: false, showPrint: false }), // 纯预览
|
|
111
|
+
print: defineMachReportPreset({ showPdfWindow: false })
|
|
112
|
+
},
|
|
113
|
+
defaultPreset: "lean"
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**优先级**:`组件 props` > `provideMachReportConfig(overlay)`(路由级响应式叠加)> `preset` > `defaults` > `内置缺省`。
|
|
118
|
+
|
|
119
|
+
**参数面板**:模板声明 `.params([...])` 或 prop `:param-defs`,组件自动生成查询条件(text / number / date / select、必填、默认值、查询 / 重置 / 回车),查询时面板值与 `props.params` 合并;编程式 `setParams(values)` 同步面板并查询。
|
|
120
|
+
|
|
121
|
+
**主题**也可不经配置中心,直接在宿主 CSS 覆写 `--mrp-shell-bg / --mrp-toolbar-bg / --mrp-btn-*`。
|
|
122
|
+
|
|
123
|
+
</details>
|
|
124
|
+
|
|
125
|
+
<details>
|
|
126
|
+
<summary><b>模板 DSL 与报表语义</b></summary>
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
const template = createTemplate()
|
|
130
|
+
.params([ // 参数面板:声明式查询条件
|
|
131
|
+
{ field: "whCode", label: "仓库", type: "select", required: true,
|
|
132
|
+
options: [{ label: "全部仓库", value: "ALL" }], defaultValue: "ALL" },
|
|
133
|
+
{ field: "date", label: "日期", type: "date", defaultValue: "2026-09-30" }
|
|
134
|
+
])
|
|
135
|
+
.page("a4", { landscape: true, margins: { marginTopMm: 12 } }) // 纸张预设 + 横向
|
|
136
|
+
.text("出库单", { leftMm: 70, topMm: 2, widthMm: 70 }, { fontSize: 16, bold: true, align: "center" })
|
|
137
|
+
.text("第 {page} 页 / 共 {totalPages} 页", { leftMm: 65, topMm: 285, widthMm: 80 }, { align: "center" })
|
|
138
|
+
.barcode("CK-001", { leftMm: 12, topMm: 14, widthMm: 40, heightMm: 12 })
|
|
139
|
+
.list("detail", { leftMm: 12, topMm: 32, widthMm: 186 }, [
|
|
140
|
+
{ header: "物料", field: "name", widthMm: 96 },
|
|
141
|
+
{ header: "数量", field: "qty", widthMm: 45, format: { kind: "number", thousands: true } },
|
|
142
|
+
{ header: "金额", field: "amount", widthMm: 45,
|
|
143
|
+
format: { kind: "number", thousands: true, digits: 2 },
|
|
144
|
+
rules: [{ when: { field: "amount", op: "<", value: 0 }, style: { color: "#cc0000" } }] },
|
|
145
|
+
{ header: "日期", field: "date", widthMm: 45, format: { kind: "date", pattern: "YYYY-MM-DD" } }
|
|
146
|
+
], {
|
|
147
|
+
groupBy: { field: "wh", headerTemplate: "仓库:{value}", subtotal: ["qty", "amount"] },
|
|
148
|
+
grandTotal: true
|
|
149
|
+
})
|
|
150
|
+
.build();
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
语义要点:格式化在进 RenderPlan 前完成(三后端零感知,`digits` 显式精确保留、日期按本地日历日解析);
|
|
154
|
+
分组 Map 归组与数据顺序无关;条件规则命中才克隆样式(热路径驻留不破坏);含占位符的文本转为页锚,每个输出页克隆注入。
|
|
155
|
+
|
|
156
|
+
</details>
|
|
157
|
+
|
|
158
|
+
<details>
|
|
159
|
+
<summary><b>数据面(fetcher 生态:零配置 / jh4j / 本地 / 自定义)</b></summary>
|
|
160
|
+
|
|
161
|
+
| 工具 | 场景 |
|
|
162
|
+
|---|---|
|
|
163
|
+
| 内置(零配置) | 插件不传 request / fetcher 时自动用全局 fetch 同源请求 `/report/codePrintReport/gridPlan` |
|
|
164
|
+
| `createFetchRequest({ baseUrl, headers, credentials, timeoutMs })` | 零依赖 fetch 适配(AbortController 超时 + 错误语义) |
|
|
165
|
+
| `createJh4jGridPlanFetcher({ request, baseUrl })` | 接宿主 axios 风格客户端,消费 jh4j gridPlan(路径 / 出入参契约兼容) |
|
|
166
|
+
| `createLocalFetcher({ [tempId]: { template, datasets } })` | 本地模板(离线 / 单测 / 无后端);面板参数用 `:param-defs="template.params"` 直取 |
|
|
167
|
+
|
|
168
|
+
自定义数据面只需实现:`type PlanFetcher = (input: { tempIds, furnitureTempId?, params }) => Promise<RenderPlan>`——
|
|
169
|
+
任何后端能吐 RenderPlan JSON 就能接(schema 有校验器与文档)。
|
|
170
|
+
|
|
171
|
+
</details>
|
|
172
|
+
|
|
173
|
+
<details>
|
|
174
|
+
<summary><b>引擎 API(框架无关,子路径按需)</b></summary>
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
import {
|
|
178
|
+
createTemplate, paginateTemplate, // 模板 DSL / 分页计算
|
|
179
|
+
renderPlan, renderPage, // RenderPlan → DOM
|
|
180
|
+
renderPlanToCanvas, createCanvasPager, // 位图渲染 / 窗口化分页器(大报表)
|
|
181
|
+
computePageWindow, validateRenderPlan, // 虚拟化窗口 / 计划校验(JSON path 定位)
|
|
182
|
+
importJh4jTemplateContent, // jh4j 模板 content → 本地模板
|
|
183
|
+
wrapText, createCanvasMeasurer // 文本测量(可注入,三端折行一致)
|
|
184
|
+
} from "@agile-team/mach-report";
|
|
185
|
+
import { renderPlanToPdf } from "@agile-team/mach-report/pdf"; // PDF 直出(pdf-lib 已内联)
|
|
186
|
+
import { renderPlanToXlsx } from "@agile-team/mach-report/xlsx"; // Excel 导出
|
|
187
|
+
import { renderDynamicSql } from "@agile-team/mach-report/sql"; // 动态 SQL(AST 校验)
|
|
188
|
+
import { createReportAdminClient } from "@agile-team/mach-report/manager"; // 管理端 API
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**渲染管线(单真相源)**:
|
|
192
|
+
|
|
193
|
+
```
|
|
194
|
+
模板 JSON ──+──> paginateTemplate(template, datasets) ──> RenderPlan
|
|
195
|
+
│ ├──> DOM 虚拟化预览
|
|
196
|
+
jh4j gridPlan ──(契约兼容消费) ├──> Canvas 窗口化分页器
|
|
197
|
+
├──> PDF 矢量直出
|
|
198
|
+
└──> print(named pages)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**大报表 Canvas**:`createCanvasPager(plan, { dpr, overscan }).attach(scrollContainer)` —— 视口窗口 + 画布池复用 + 每帧限量绘制 + resize 自适应。
|
|
202
|
+
|
|
203
|
+
**批量打印**:`printPlans([plan1, plan2], { onProgress })` 多单合并单文档(混合纸张 named pages),只弹一次打印框。
|
|
204
|
+
|
|
205
|
+
</details>
|
|
206
|
+
|
|
207
|
+
<details>
|
|
208
|
+
<summary><b>性能实测</b></summary>
|
|
209
|
+
|
|
210
|
+
| 指标 | jh4j 现状(实测感知) | MachReport 实测 |
|
|
211
|
+
|---|---|---|
|
|
212
|
+
| 分页 5,000 行(139 页) | 秒级(全量 DOM) | **~57ms**(单测预算门锁定) |
|
|
213
|
+
| 100 页含千行明细首屏 | 秒级 | < 300ms(虚拟化 + Canvas 窗口化) |
|
|
214
|
+
| 缩放 / 翻页 | 触发重排 | < 16ms(transform 矩阵变换) |
|
|
215
|
+
| Canvas 大报表内存 | — | **O(窗口)**(A4@dpr2 ≈14MB/页,全量渲染不可行) |
|
|
216
|
+
| 打印 / PDF 导出 | 后端往返 1-3s | < 500ms(前端矢量直出 + 字体子集化缓存) |
|
|
217
|
+
| 引擎主入口体积 | 随 jh4j 整包 | ~27KB min(零依赖可摇树) |
|
|
218
|
+
|
|
219
|
+
</details>
|
|
220
|
+
|
|
221
|
+
<details>
|
|
222
|
+
<summary><b>Node 无头导出通道(同一引擎两端执行)</b></summary>
|
|
223
|
+
|
|
224
|
+
引擎框架无关——`renderPlanToPdf` / `renderPlanToXlsx` 及 `loadFontWithCache` 均可在 **Node 18+** 直接运行
|
|
225
|
+
(无 IndexedDB 环境自动降级内存缓存),解锁服务端场景而**不需要另写一套渲染**:
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
// server.ts —— 定时任务 / 邮件推送 / 归档,无浏览器环境
|
|
229
|
+
import { renderPlanToPdf } from "@agile-team/mach-report/pdf";
|
|
230
|
+
import { loadFontWithCache } from "@agile-team/mach-report/pdf";
|
|
231
|
+
import { renderPlanToXlsx } from "@agile-team/mach-report/xlsx";
|
|
232
|
+
|
|
233
|
+
const font = await loadFontWithCache("/path/to/simhei.ttf"); // 读文件系统可自行替换
|
|
234
|
+
const { bytes } = await renderPlanToPdf(plan, font ? { customFontBytes: font } : {});
|
|
235
|
+
await fs.writeFile("archive.pdf", bytes);
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
与 jh4j"服务端另写一套渲染"本质不同:**一份渲染代码两端执行**,服务端只做无头宿主(定时生成、批量归档、合规留档回传)。
|
|
239
|
+
|
|
240
|
+
</details>
|
|
241
|
+
|
|
242
|
+
<details>
|
|
243
|
+
<summary><b>从 jh4j 迁移(契约对照)</b></summary>
|
|
244
|
+
|
|
245
|
+
| 契约项 | 对齐方式 |
|
|
246
|
+
|---|---|
|
|
247
|
+
| 组件 props / 事件 / ref | `temp-id / furniture-temp-id / params / height / auto-load / show-*`;`loaded / error`;`reload / print / exportAs / openPdfWindow / gotoPage / setParams` |
|
|
248
|
+
| 数据接口 | `/report/codePrintReport/gridPlan` 路径与出入参兼容 |
|
|
249
|
+
| 存量模板 | `importJh4jTemplateContent` 逆向 schema 直接转换(含告警清单) |
|
|
250
|
+
| 存量 SQL | `#{}` `${}` `{if}` 三语法零改写;AST 级单 SELECT 校验加严 |
|
|
251
|
+
| 模块联邦 | expose 名对齐,宿主配 `/sub/mach-report/` 网关即可灰度共存 |
|
|
252
|
+
|
|
253
|
+
</details>
|
|
254
|
+
|
|
255
|
+
<details>
|
|
256
|
+
<summary><b>打印兼容性指引(实战排障)</b></summary>
|
|
257
|
+
|
|
258
|
+
| 现象 | 原因与处理 |
|
|
259
|
+
|---|---|
|
|
260
|
+
| 表格底色打印丢失 | 浏览器默认关闭"背景图形"——打印对话框勾选*背景图形* |
|
|
261
|
+
| 混合纸张按 A4 输出 | named pages 需较新内核(Chrome 85+ / Safari 16+ / Firefox 133+),旧内核退化统一纸张 |
|
|
262
|
+
| iOS App 内打印无效 | WKWebView 限制——引导用户走"分享 → 打印"或导出 PDF |
|
|
263
|
+
| 字体首载后失效 | PDF 字体走 IndexedDB 缓存(15s 超时回退内置西文字体),清理站点数据后重新拉取 |
|
|
264
|
+
|
|
265
|
+
</details>
|
|
266
|
+
|
|
267
|
+
<details>
|
|
268
|
+
<summary><b>架构与质量门禁</b></summary>
|
|
269
|
+
|
|
270
|
+
```
|
|
271
|
+
MachReport(pnpm monorepo · TS strict · 单 npm 包)
|
|
272
|
+
├── packages/
|
|
273
|
+
│ ├── mach-report/ # 唯一发布包(零运行时依赖,ESM+CJS)
|
|
274
|
+
│ │ └── src/
|
|
275
|
+
│ │ ├── layout/ render/ schema/ builder/ compat/ format.ts # 引擎核心
|
|
276
|
+
│ │ └── pdf/ sql/ manager/ xlsx/ vue/ # 子路径域(重依赖/框架按需隔离)
|
|
277
|
+
│ └── federation/ # 模块联邦远程入口(部署产物,不发 npm)
|
|
278
|
+
├── examples/ # minimal 演示 + fed-host 联邦宿主实证
|
|
279
|
+
├── e2e/ docs/ assets/
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
pnpm typecheck && pnpm lint && pnpm test && pnpm build && pnpm exec playwright test
|
|
284
|
+
# TS strict(含 vue-tsc) · ESLint 0 错 0 警 · 全量单测(纯逻辑 node 环境分流) · 真 Chromium E2E(0 重试) · 七入口构建
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
关键设计决策:单真相源渲染 / 单包子路径架构 / 自引用复用 / 三后端共享几何与样式解析 / 性能预算门禁 / 变更日志(changesets)。
|
|
288
|
+
|
|
289
|
+
</details>
|
|
290
|
+
|
|
291
|
+
## 文档
|
|
292
|
+
|
|
293
|
+
- [docs/PROGRESS.md](docs/PROGRESS.md) —— 十一轮迭代日志与决策记录
|
|
294
|
+
- [docs/API.md](docs/API.md) —— 接入速查(props / 配置中心 / 数据面 / DSL)
|
|
295
|
+
- [docs/reverse-findings.md](docs/reverse-findings.md) —— jh4j 逆向结论
|
|
296
|
+
- [AGENTS.md](AGENTS.md) —— 仓库协作指南(命令 / 架构 / 发布流程)
|
|
297
|
+
|
|
298
|
+
## 路线图
|
|
299
|
+
|
|
300
|
+
- v1.0(已完成):单包架构 · 报表语义四件套 · 参数面板 · 交互完整面 · 双远端 + npm 定版
|
|
301
|
+
- 设计器画布(拖拽 / 属性面板 / 撤销栈)——独立立项,规划中
|
|
302
|
+
- 管理端控制台 UI(`./manager` API 已就绪)——独立立项,规划中
|
|
303
|
+
- 交叉表 / 公式列——按真实需求排期
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
<div align="center">
|
|
308
|
+
|
|
309
|
+
**Mach 家族**:[MachTable](https://www.npmjs.com/package/@agile-team/mach-table)(数据表格)→ **MachReport**(打印报表)
|
|
310
|
+
|
|
311
|
+
Source-available © ChenyCHENYU (Agile Team). 使用需事先取得书面授权,详见 [LICENSE](LICENSE)。
|
|
312
|
+
|
|
313
|
+
</div>
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
**v1.1.0** · 单包零依赖 · 质量门禁全绿 · 语义化版本演进
|
package/package.json
CHANGED