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

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

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

16 KiB
Raw Blame History

工作进度-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):

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


九、遗留与后续建议

  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/* 死代码。