质量模块: - 11 张工艺执行书表:封面、试产、选剥茧(含混茧明细)、试缫与大样、煮茧(含分车间配温、副煮车间)、自动缫(含大样)、后缫 - 10 张样茧试缫表:原料小样/综合样主表 + 选茧、解舒、茧幅、切剖、清洁洁净、茧丝百回纤度 - 服务与接口、主控指针回写(列表 √ 标记)、封面编号自增、报表数据包 - 前端 8 张报表打印版式、7 类工艺单据与 6 类调查录入弹窗、字段驱动表单/表格组件 - 菜单种子新增「质量」目录,样茧子表增加 YjssType 判别列,避免原料小样与综合样自增 Id 重号串数据 报表中心: - 报表模板管理、锐浪 grhtml5 加载与预览、全屏设计面板、数据源 Provider(落丝记录、职工花名册) 其他:同步工作进度 1-11 文档、开发数据库快照、.gitignore 忽略运行日志目录
16 KiB
工作进度-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
一、本轮目标
- 把报表渲染链路从 Grid++Report 6 整体换成 Grid++Report 7(GR6 的浏览器插件方案已被 Chrome/Edge 移除)
- 模板不再"改完先保存再上传 .grf",而是作为报表的一个属性实例,与配置/参数一次保存
- 参考 F9 实体设计,把模板从报表主表拆成独立实体
ReportFile(Report.ReportFileId关联) - 按 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.DesignContentGET /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):
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 ✅ |
启动方式不变:
cd backend/F9WebAiTest.Api && dotnet run
cd web && npm run dev
默认账号:admin / 123456
九、遗留与后续建议
Report_FavoriteGroup未实现:用户提供的 F9 实体(User_Id/Parent_Id/Name/Sort)对应「报表收藏分组」,本轮聚焦模板实例化与内嵌设计器,未落地。- 模板编辑能力边界:锐浪设计器是 COM 组件,浏览器无法嵌入;自研的网页画布在排版精度上达不到要求,已移除。模板排版统一在 Grid++Report 7 设计器完成 → 上传。
- 无网页端模板编辑:如需在系统内改模板,需下载
.grf→ 本机设计器修改 → 上传覆盖;或走f9designer://本机设计器协议(自动回传)。 - 历史废弃列:
sys_reports.template_data / template_name / template_size仍在表中(值已清空),确认无回滚需求后可DROP COLUMN。 - 编辑器与报表中心分叉:报表中心的参数控件配置(左栏「参数」tab)目前是纯文本填
ControlType,未接utils/gridreport/controls注册中心的分组下拉(原editor.vue有,随删除一起丢失,可补回)。 - 工作进度-3 遗留仍在:
Number()精度隐患、adminRolePermission重复、部门权限无消费方、system/*死代码。