Files
yanpeng200212 a8890c81e9 新增质量模块(工艺执行书 / 样茧试缫)与报表中心
质量模块:
- 11 张工艺执行书表:封面、试产、选剥茧(含混茧明细)、试缫与大样、煮茧(含分车间配温、副煮车间)、自动缫(含大样)、后缫
- 10 张样茧试缫表:原料小样/综合样主表 + 选茧、解舒、茧幅、切剖、清洁洁净、茧丝百回纤度
- 服务与接口、主控指针回写(列表 √ 标记)、封面编号自增、报表数据包
- 前端 8 张报表打印版式、7 类工艺单据与 6 类调查录入弹窗、字段驱动表单/表格组件
- 菜单种子新增「质量」目录,样茧子表增加 YjssType 判别列,避免原料小样与综合样自增 Id 重号串数据

报表中心:
- 报表模板管理、锐浪 grhtml5 加载与预览、全屏设计面板、数据源 Provider(落丝记录、职工花名册)

其他:同步工作进度 1-11 文档、开发数据库快照、.gitignore 忽略运行日志目录
2026-10-22 10:30:37 +08:00

272 lines
16 KiB
Markdown
Raw Permalink 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.
# 工作进度-4:Grid++Report 6 → 7 替换 + 模板实例化(ReportFile)+ 页面内嵌报表设计工作台
> 时间:2026-10-20(承接工作进度-3)
> 环境:后端 .NET 8 + FreeSql + MediatR(Windows/SQLite),前端 Vue3 + Element Plus + Vite
> 本机 Grid++Report:**7.0**(`D:\GridReport7`,设计器 `gr7designer64.exe`,WEB 客户端 `gr7webapp64.exe`)
> 参考:锐浪 GR7 官方 `Samples/Web/vue`、`Samples/Web/html/html5`;F9 实体 `Report.cs` / `ReportFile.cs` / `Report_FavoriteGroup.cs`
---
## 一、本轮目标
1. 把报表渲染链路从 **Grid++Report 6** 整体换成 **Grid++Report 7**(GR6 的浏览器插件方案已被 Chrome/Edge 移除)
2. 模板不再"改完先保存再上传 .grf",而是**作为报表的一个属性实例**,与配置/参数**一次保存**
3. 参考 F9 实体设计,把模板从报表主表拆成独立实体 **`ReportFile`**(`Report.ReportFileId` 关联)
4. 按 WinForms「编辑报表」的嵌入形态,做**页面内嵌的报表设计工作台**,不再唤起外部设计器程序
---
## 二、GR7 替换(技术结论先行)
| 项 | GR6 | GR7 |
|---|---|---|
| 网页内渲染库 | `grhtml5-6.8-min.js`(6.8.24.0119) | **`grhtml5-7-min.js`(7.0.26.0930)** |
| 渲染入口 | `rubylong.grhtml5.insertReportViewer(id, tpl, data)` | **API 完全一致**(可直接替换) |
| Web 报表客户端 | `grwebapp6.exe`,协议 `grwebapp://`,WS 端口 **22333** | `gr7webapp64.exe`,协议 **`gr7webapp://`**,WS 端口 **22337** |
| 设计器 | `grdesigner6.exe` | `gr7designer64.exe` |
| `.grf` 格式 | UTF-8 BOM + JSON | **新增 `_WR7_` 压缩格式**(`EF BB BF` + `_WR7_w...`),也兼容旧 JSON;库内 `gr.wr.decodeWR()` 解码 |
**关键判断**:`.grf` 里的 `Version` 保持 `6.8.5.0` 不动——GR7 自带样例全是 6.6.8.1,说明向下兼容确定;`grhtml5-7-min.js` 中 `Version` 只是 `grconst.VERSION` 常量,不校验文件版本。
### 改动清单
| 文件 | 改动 |
|---|---|
| `web/public/grlib/grhtml5-7-min.js` | 新增(官方 270KB),删除 `grhtml5-6.8-min.js` |
| `web/src/utils/gridreport/loadGrhtml5.js` | 加载 `/grlib/grhtml5-7-min.js` |
| `web/src/utils/gridreport/grwebapp.js` | 整份换 GR7 官方版(`gr7webapp://` / 22337 / v7 客户端下载 URL),启用 ESM 导出 |
| `tools/report-designer/F9ReportDesignerHandler.ps1` | 设计器优先 `D:\GridReport7\gr7designer64.exe`,其次 .grf 关联(只认 gr7designer),最后回退 GR6 |
| `tools/report-designer/install-gr7-grf-assoc.reg` | 新增:`.grf` → `GR7.Template` → `gr7designer64.exe`(HKCU 用户级,已导入生效) |
### 顺带修掉的两个真缺陷
| # | 问题 | 说明 |
|---|---|---|
| 1 | `viewer.Stop()` 一直是空操作 | 官方 API 是**小写 `stop()`**(GR6/GR7 都是),原来被 `try/catch` 吞掉 → 切 Tab / 关预览从未真正销毁渲染实例。改为 `(viewer.stop \|\| viewer.Stop).call(viewer)` |
| 2 | `urlAddRandomNo()` 污染 token | URL 已带 `?access_token=xxx` 时再拼 `?id=`,浏览器把 `xxx?id=123` 整体当 token → 客户端打印/导出 401。改为已有查询串时用 `&` |
---
## 三、模板保存方式:随报表保存 + 上传/下载 .grf(两种并存)
> 中途做过"只随报表保存、去掉上传/下载"的版本,后按需求**恢复上传/下载 .grf 的方式**,并保留"随报表保存"用于页面内画布编辑落库。
### 3.1 后端
`ReportCreateDto` / `ReportUpdateDto` 增加 `TemplateJson`(.grf 的 JSON 文本);`CreateAsync/UpdateAsync` 内一并落库,**不传则保留原模板**(启停等只改配置的请求不会清模板)。
模板文本规范化 `NormalizeTemplateJson()`:去 BOM → trim → 校验必须是 JSON 对象 → 否则 400「报表模板(.grf)不是合法 JSON」。
独立上传/下载接口保持不变:
- `POST /api/sys/report/{id}/template`(multipart,`IFormFile`)→ 解码 UTF-8、去 BOM → 写 `ReportFile.DesignContent`
- `GET /api/sys/report/{id}/template/download` → 输出带 UTF-8 BOM 的 `.grf`,文件名 `{编码}.grf`
### 3.2 前端(两条路径)
| 路径 | 入口 | 提交方式 |
|---|---|---|
| A. 页面内编辑 | 中栏「页面设计」画布 / 「源码 JSON」 | `save()` 时随配置一起 `templateJson` 提交 |
| B. 锐浪设计器设计好后上传 | 工作台顶栏「上传 .grf / 下载 .grf」;列表卡片「上传模板 / 下载模板」 | 走独立接口,**不走保存**;上传成功后画布自动重新载入 |
- 工作台:`uploadGrf()` 选本地 `.grf` → `POST /{id}/template` → `reloadTemplateFromServer()` 重载画布;`downloadGrf()` 用 `access_token` 直开下载地址
- 列表页:隐藏 `input[type=file][accept=".grf"]` + `pickUpload(row)` / `downloadTemplate(row)`;若该报表正在右侧编辑,上传后 `designerKey++` 强制工作台重新载入
---
## 四、实体重构:Report + ReportFile(对齐 F9)
### 4.1 新实体
`Domain/Entities/SYS/Reports/ReportFile.cs`(表 `sys_report_files`,基类 `BusinessEntity`):
```csharp
public class ReportFile : BusinessEntity
{
public long ReportId { get; private set; }
public string DesignContent { get; private set; } = ""; // .grf 的 JSON 文本
public void UpdateContent(string designContent) { ... }
}
```
`Report` 增加 `ReportFileId`(对齐 F9 `Report.ReportFileId`)+ `BindReportFile(long)`,原 `TemplateData/TemplateName/TemplateSize` 标记**废弃**(仅供启动迁移读取,不再写入),新增 `ClearLegacyTemplate()`。
### 4.2 服务层
| 方法 | 说明 |
|---|---|
| `GetTemplateContentAsync(Report)` | 读 `ReportFile.DesignContent`;无模板返回 null |
| `SaveTemplateAsync(Report, json)` | 有实例则 `UpdateContent`,否则新建实例并 `BindReportFile`;**只改内存**,由调用方统一保存 |
| `FindTemplateFileAsync(Report)` | 优先按 `ReportFileId` 查,兜底按 `ReportId` 查 |
| `DeleteAsync` | 级联删除模板实例 |
| `HasTemplate(r)` | `r.ReportFileId != 0` |
Controller 侧模板输出统一补 **UTF-8 BOM**(`ToGrfBytes()`),保证下载给设计器/浏览器渲染都符合 .grf 约定;**数据库里存纯文本**(不带 BOM),避免"套娃 BOM"。
### 4.3 旧数据自愈
`DbSeeder.MigrateReportTemplateToFile()` 在启动时把 `sys_reports.template_data`(旧内嵌列)迁移成 `ReportFile` 并回填 `ReportFileId`,随后清空废弃字段。同时 `EnsureColumn(fsql, "sys_reports", "report_file_id", ...)` 兜底旧库加列(`SyncStructure` 不会为已存在表加列)。
实测启动日志:
```
[DbSeeder] 报表「职工花名册」模板已迁移到 ReportFile#139204832664752128
[DbSeeder] 报表「落丝记录」模板已迁移到 ReportFile#139204832731860992
```
---
## 五、报表编辑页(配置 + 参数 + 模板预览)
> **重要变更**:先用"自研网页可视化设计器"做过一版(画布 + 对象树 + 属性面板),因达不到锐浪设计器的排版能力,**已整体移除**,模板改回"上传/下载维护"。
### 5.1 页面结构
`/report/manage` 为 **左列表 + 右编辑面板**:
```
┌──────────────┬──────────────────────────────────────────────────────┐
│ 报表列表 │ 顶栏:编辑报表·名称 [模板已配置] [删除][上传 .grf][下载 .grf]
│ ┌──────────┐ ├─────────────┬────────────────────────────────────────┤
│ │落丝记录 │ │ 配置 | 参数 │ [预览视图][模板源码] │
│ │PROD_QS_1 │ │ │ │
│ │模板已配置 │ │ 分组/编号 │ ┌──── grhtml5 网页内渲染 ────┐ │
│ │上传 下载 删除│ │ 名称/顺序 │ │ 日期 车间 机组 庄口 回数 ... │ │
│ └──────────┘ │ 数据源/SQL │ └─────────────────────────────┘ │
│ ┌──────────┐ │ 备注 │ │
│ │职工花名册 │ │ Config JSON │ 模板源码:只读查看 .grf JSON │
│ └──────────┘ │ │ │
└──────────────┴─────────────┴────────────────────────────────────────┘
```
### 5.2 功能边界
| 区域 | 内容 |
|---|---|
| 左栏·配置 | 分组 / 编号 / 名称 / 顺序 / 数据源 `DsName` / 数据 SQL / 备注 |
| 左栏·参数 | 查询参数列表(参数名、标签、控件类型、默认值、`Ds_Service`、`Ds_Static`、必填) |
| 左栏·Config JSON | 自动模式随表单一同生成,可切「源码」直接写 |
| 右栏·预览视图 | grhtml5 渲染真实数据;未上传模板时显示占位提示 + 上传按钮 |
| 右栏·模板源码 | 只读查看 `.grf` JSON,用于核对字段名 |
| 顶栏 | 删除 / 上传 .grf / 下载 .grf / 刷新预览 / 保存 |
模板本身**不在网页里编辑**——用 Grid++Report 7 设计器排版 `.grf`,再上传。
### 5.3 文件
| 文件 | 说明 |
|---|---|
| `web/src/components/report/ReportEditor.vue` | 新增:配置 + 参数 + 预览 + 模板源码(只读) |
| `web/src/components/report/ReportDesigner.vue` | **已删除**(自研画布/对象树/属性面板) |
| `web/src/views/report/manage/index.vue` | 重写:左列表卡片(名称/编码/分组/模板状态/上传/下载/删除)+ 右侧编辑面板 |
| `web/src/views/report/manage/editor.vue` | **已删除**(旧的全屏抽屉版) |
保存语义:编辑已有报表时**不提交模板**,避免覆盖刚上传的 `.grf`;仅新建时写入一份最小兜底模板。
---
## 六、踩坑记录(本轮)
| # | 问题 | 根因 | 解决 |
|---|---|---|---|
| 1 | 后端改完编译报 `MSB3027/MSB3021` 文件被锁 | `dotnet run` 进程占用 `bin` 下 dll | 先 `Stop-Process` 停后端再 build(编译本身 0 错误,只是拷贝失败) |
| 2 | 模板读回来变空、字段列表丢失 | 服务端存 .grf 带 UTF-8 BOM,axios 的 `JSON.parse` 失败后**静默返回字符串**,前端再 `JSON.parse` 遇 BOM 抛错 → 走 `catch` 得到空模板 | 存储改纯文本,输出时补 BOM;前端 `parseTemplatePayload()` 统一去 BOM |
| 3 | 旧库加了字段但服务端读不到 | FreeSql `CodeFirst.SyncStructure` **不会给已存在表加列** | `EnsureColumn(fsql, "sys_reports", "report_file_id", "INTEGER NOT NULL DEFAULT 0")` |
| 4 | 迁移后再也读不到旧模板 | 实体属性删了就读不到 `template_data` 列 | 保留 `TemplateData` 属性标废弃,仅供迁移读取,迁移后 `ClearLegacyTemplate()` |
| 5 | 保存后模板体积从 4300B 变 2911B | 前端 `JSON.stringify(tpl)` 无缩进,缩进空白被去掉 | 非缺陷,属预期(`JSON.stringify(obj, null, '\t')` 才带缩进) |
| 6 | 画布拖动无效 | `startDrag(e, path, mode, event)` 定义 4 参,模板只传 3 参 | 签名改为 `startDrag(path, mode, event)` |
| 7 | 对象树 `default-expanded-keys` 不生效 | 节点没有 `node-key` | 每个节点补 `pathKey = JSON.stringify(path)` |
| 8 | **上传 .grf 报「不是合法 JSON」** | **GR7 设计器保存的 .grf 已不是 JSON**:`EF BB BF` + `_WR7_w...` 压缩文本(抽样 D:\GridReport7\Samples 1102 个 .grf:970 个 `_WR7_` 格式,130 个旧 JSON 格式) | 后端不再做 JSON 校验,只去 BOM/空白原样存储;`DecodeTemplateBytes()` 按 BOM 识别 UTF-8/UTF-16LE/UTF-16BE |
| 9 | 上传后预览报「获取报表模板失败 URL: 乱码」 | GR7 库用 `F(e)` 判断模板来源:只有 `_WR_`(4 字符,下划线)开头才当"内容",而设计器存的是 `_WR7_` → 被判为 URL 去请求 | 预览改传**模板 URL**(`/template.json?access_token=`),走库的 ajax → `Report.load()` 分支,内部 `"_WR"==substr(0,3)` 命中后 `decodeWR()` 正常解码 |
| 10 | `/template.json` 返回带 BOM 会破坏前缀判断 | `\uFEFF` 开头使 `"_WR"==substr(0,3)` 与 `"{"===charAt(0)` 双双失效 | `template.json` **不输出 BOM**;仅 `/template/download`(给设计器的文件)带 BOM |
---
## 七、验证记录
### 7.1 接口层(node 脚本直连 `localhost:3000`)
```
CREATE_HAS_TPL=true NAME=tmp_tpl_351543.grf SIZE=626
TPL_BOM=efbbbf TPL_TITLE=TMP 随报表保存 FIELDS=A,B
NO_TPL_UPDATE_HAS_TPL=true SIZE=626 ← 不带模板的修改不会清模板
WITH_TPL_UPDATE_TITLE=改过的标题 ← 带模板一起改
BAD_TPL_CODE=400 MSG=报表模板(.grf)不是合法 JSON
```
迁移 + 模板随报表保存(重启后):
```
LIST=PROD_QS_1(file=139204832731860992,has=true) | employee_directory(file=139204832664752128,has=true)
TPL_BOM=efbbbf LEN=4300
UPDATE_CODE=200
AFTER_AUTHOR=F9 内嵌设计器
KEEP_TPL_LEN=2911 ← 不带模板的 PUT 后模板仍在
```
### 7.2 报表中心(GR7 渲染)
```
GR_SCRIPTS=["/grlib/grhtml5-7-min.js"]
HOLDER_HTML_LEN=240770 HOLDER_CHILDREN=3
职工花名册 部门:(全部) 关键字:(全部) 记录数:942
工号 职工姓名 性别 部门 工种 电话 入职日期 / 付华芳 女 丝业生产中心 ...
ALERT=NONE
```
### 7.3 内嵌设计工作台
```
LIST_ITEMS=2 TOPBAR=编辑报表 · 落丝记录
VIEW_TABS=["页面设计","明细字段","源码 JSON","预览视图"]
LEFT_TABS=["配置","参数 (0)"]
PAPER_EXISTS=true W=793.8px ← A4 21cm
SECTIONS=["报表头","页眉","明细网格","页脚"]
CONTROLS=4 DETAIL_CELLS=36
PROPS_TITLE=属性 · StaticBox#0
PROPS=["Type","Name","Center","Left","Top","Width","Height","Text"]
CONTROLS_AFTER_INSERT=5 → CONTROLS_AFTER_DELETE=4
PREVIEW_TEXT=日期 车间 机组 庄口 回数 颜色 半车 落丝时间 缫丝桶数
```
### 7.4 编译
- 后端 `dotnet build`:**0 错误**(仅有既存 CS8632 nullable 注释警告)
- 前端 `vite build`:通过(仅 `@microsoft/signalr` 的既有 Rollup 注释警告)
---
## 八、当前状态
| 项 | 状态 |
|---|---|
| 报表渲染库 | GR7(`grhtml5-7-min.js` 7.0.26.0930)✅ |
| WEB 客户端打印/PDF/Excel | GR7(`gr7webapp://` / 22337)✅ |
| 本机设计器协议 | 指向 `gr7designer64.exe`,`.grf` 关联已切 GR7 ✅ |
| 模板存储 | 独立实体 `ReportFile.DesignContent`,`Report.ReportFileId` 关联 ✅ |
| 新增/编辑报表 | 配置 + 参数 + 模板一次保存;**同时保留 .grf 上传/下载** ✅ |
| 旧数据 | 启动自动迁移到 `ReportFile`,日志可查 ✅ |
| 报表管理页 | 左列表 + 右编辑面板(配置/参数/预览/模板源码只读),无自研设计器 ✅ |
| 报表中心 | 多 Tab 预览正常(942 条职工)✅ |
| 后端服务 | 运行中 `http://localhost:3000`(本轮重启后可正常工作)✅ |
| 前端服务 | 运行中 `http://localhost:5173` ✅ |
启动方式不变:
```powershell
cd backend/F9WebAiTest.Api && dotnet run
cd web && npm run dev
```
默认账号:`admin / 123456`
---
## 九、遗留与后续建议
1. **`Report_FavoriteGroup` 未实现**:用户提供的 F9 实体(`User_Id` / `Parent_Id` / `Name` / `Sort`)对应「报表收藏分组」,本轮聚焦模板实例化与内嵌设计器,未落地。
2. **模板编辑能力边界**:锐浪设计器是 COM 组件,浏览器无法嵌入;自研的网页画布在排版精度上达不到要求,已移除。模板排版统一在 Grid++Report 7 设计器完成 → 上传。
3. **无网页端模板编辑**:如需在系统内改模板,需下载 `.grf` → 本机设计器修改 → 上传覆盖;或走 `f9designer://` 本机设计器协议(自动回传)。
4. **历史废弃列**:`sys_reports.template_data / template_name / template_size` 仍在表中(值已清空),确认无回滚需求后可 `DROP COLUMN`。
5. **编辑器与报表中心分叉**:报表中心的参数控件配置(左栏「参数」tab)目前是纯文本填 `ControlType`,未接 `utils/gridreport/controls` 注册中心的分组下拉(原 `editor.vue` 有,随删除一起丢失,可补回)。
6. **工作进度-3 遗留仍在**:`Number()` 精度隐患、admin `RolePermission` 重复、部门权限无消费方、`system/*` 死代码。