状态:**待确认**。本文只做方案,不动代码。
触发背景:AI 导入已能正确选到GroupedQualityTable(选型问题已修,见开发进度 §二十四),
但用户比对原件后指出「草稿仍不像原件」。经核查,剩下的差距**全部是「可用积木的表达力不够」**,
不是识别问题——文字都抽出来了,是组件装不下。
用户原件《百事模版.docx》(142373 字节)与当前草稿的逐项对比:
| 原件里有的 | 草稿里的下场 | 原因 |
|---|---|---|
表格列「检测方法」(GB 5009.3-2016 / ANA.MTH-000034) |
整列丢弃 | GroupedQualityTable 的 7 列写死在代码常量里 |
表格列「结论」(合格Checkout) |
整列丢弃 | 同上 |
| 两级表头「检验结果 | 结论」 | 拍平 | 组件只支持单层表头 |
| 样品信息 7 项(产品名称 / 产品数量 / 规格 / 生产日期 / 批号 / 土豆品种 / 有效日期) | 只剩 6 项,且与原件的 6 项**不是同一批** | SampleInfo 的字段写死在代码常量里 |
首行栏目名 产品名称Material Names |
模型拿通用组件硬凑成独立一行 | 没有任何组件能承接「表头里的栏目名」 |
两处「写死」的位置:
mom-pro2-before/src/components/quality/inspection/grouped-quality-table.ts 的 COLUMNS 常量(7 项)mom-pro2-before/src/components/quality/header/sample-info.ts 的 SAMPLE_FIELDS 常量(6 项)QualityProps = Record<string, boolean | number | string | undefined>,QualityFieldType 只有 boolean | enum | number | string | text 五种。
属性最终落到画布节点的 attributes 上(写作 data-qc-<key>),而ReportTemplateSchema.QualityComponentNode.attributes 也是字符串映射。
⇒ 新增一个「列数组」属性类型要动协议本身(属性面板控件、画布序列化、schema 契约测试、
两侧渲染器),代价远超收益。列定义必须能被编码成**一个标量字符串**。
这是好消息,已核查:
HtmlRenderer.renderAttributes 只做两件与组件无关的事:跳过 data-quality-type / data-qc-*、on* 与危险协议;**没有任何按组件类型分支的逻辑**,也没有属性白名单。CanvasSafety.validate 是黑名单(script 等可执行标签、事件属性、整段 CSS),colspan / rowspan 本来就放行。data-qc-repeat / data-qc-repeat-row 这两个通用属性驱动。⇒ 本轮不需要改 HtmlRenderer、BASE_CSS、CanvasSafety,也不需要改 schema 版本号。
组件侧改动集中在「注册表里的一个 definition」。这也正是质量组件协议的设计初衷
(core/types.ts 开头:「新增质量组件=注册一个新 definition,不需要改动 GrapesJS 集成代码」)。
推荐做法:给 GroupedQualityTable 加两个**可选**属性 columns / headerSpans;给 SampleInfo
加一个**可选**属性 fields。
buildContent 里回落到现有常量)⇒ 存量和已发布模板零影响,defaults。QualityTable / GroupedQualityTable),单行字符串,列与列之间用 | 分隔,每列写作 列标题=绑定表达式:
序号={{index}}|检验项目={{item.itemName}}|检测方法={{item.checkMethod}}|标准要求={{item.standardValue}}|实测值={{item.actualValue}}|单位={{item.unit}}|判定={{item.resultText}}
三条约束,都来自 2.1:
= 只作第一个分隔符:标题与表达式都不允许含 =,表达式里的 {{...}} 原样保留。|;需要竖线时用全角 |。纵向合并列(分组表专用):列标题前加 # 表示「这一列要跨住整组」,沿用现有GroupedQualityTable 的 rowspan / hidden 机制:
#检验项目={{item.itemName}}|子项={{item.group.childName}}|...
headerSpans 描述**上面那一层**表头,单元格写作 标题^跨越列数:
检验结果^3|结论^2
下层表头由 columns 里各列的标题自动拼出(顺序即列顺序)。约束:
各单元格跨越列数之和必须等于 columns 的列数,否则保存时给出精确报错:
表头跨列数合计 5,检验表的实际列数为 7,两者必须相等。请检查「表头跨列」里各段末尾的
^数字。
(按 error-message-precision:给出具体维度 + 两侧实际值 + 可执行动作。)
同一个语法,用在 SampleInfo 上:
样品编号={{report.sampleNo}}|产品名称={{report.productName}}|规格={{report.spec}}|生产日期={{report.produceDate}}|批号={{report.batchNo}}|土豆品种={{report.potatoVariety}}|有效日期={{report.expireDate}}
每行放几组仍由现有的 pairsPerRow 属性控制,布局逻辑不变。
这是本轮最容易漏掉、且**不修则新功能形同虚设**的一条。
| 层 | 现状 |
|---|---|
MES 单据侧 MesQcReportItemRespDTO |
已有 checkMethod(检测方法)、tool(检测工具) |
qcreport 引擎 InspectionItem |
没有这两个字段 |
ReportContext.itemMap() 下发的前端作用域 |
只有 index / itemCode / itemName / standardValue / actualValue / unit / requirement / group.* / upperLimit / lowerLimit / result / resultText / remark |
⇒ 就算列定义里写了 检测方法={{item.checkMethod}},渲染期也取不到值,只会按「绑定取不到值」
记一条缺口、留一片空白。
须补的链路(缺一不可,且两侧字段名必须逐字一致):
MesQcReportContextMapper:把 DTO 的 checkMethod(必要时含 tool)映射进 InspectionItemInspectionItem:加字段 + 进 copy()ReportContext.itemMap():下发到前端作用域ReportContextCodec:冻结快照的读写(否则历史报告重渲染时丢值)engine/context.ts 的 ReportContextItem:镜像同名字段注意:resultText(判定)是**引擎算出来的**(InspectionItem.applyResult),不是 MES 给的;
「结论」列如果要的是人工拍的结论,还要确认它是否已有下发(MesQcReportContextMapper 注释提到
「人工拍的检验判定,与引擎算出的 result/resultText/conclusion 分列两处」)。这一条**待确认后再定**。
| 备选 | 否掉的原因 |
|---|---|
| 列的列定义用 JSON 字符串 | 模型要输出转义后的嵌套 JSON("columns":"[{\"label\":…}]"),实测这类转义是模型出错的重灾区;属性面板里人也没法手改。单行语法信息量相同而两边都好写。 |
给 QualityFieldType 加数组类型 |
要动协议、属性面板、画布序列化、schema 契约测试,代价远超收益(见 2.1)。 |
| 新增独立的「可自定列检验表」组件 | 与 QualityTable / GroupedQualityTable 三方竞争,选型稳定性风险大(见 3.1);用户还要维护两套同类组件。 |
| 做一个可视化的列编辑器(增删拖拽) | 收益确实更高,但成本是前者的数倍,且需要新的属性面板控件类型。**建议先用手填字符串验证价值**,真有需求再迭代——那时字符串语法已经是现成的存储格式,不用迁移。 |
把 SampleInfo 的 dataSchema 改成动态 |
绑定选择器只能枚举静态 dataSchema,动态化会让「选数据字段」入口失效。**保持静态**:columns/fields 属性设为**不可绑定**(bindable 不声明),由人手写完整串。 |
新属性必须让模型能填,否则 AI 导入仍产不出这些列。
buildQualityComponentCatalog() 把 propertySchema 原样映射成 fields,type: 'text' 即自动进提示词,**不需要改 catalog.ts**。text 类型(而不是自造一个新类型)还能让属性面板直接渲染成多行输入框,零改动。columns 把这些列按 标题=绑定 写进去」,aiHint 必须重写:GroupedQualityTable 的 hint 要说明「列与本组件默认不同时用 columns 覆盖」,GroupedQualityTable 选对不等于报告对了。前端(mom-pro2-before/src/components/quality/)
inspection/grouped-quality-table.ts:propertySchema 加 columns / headerSpans(type: 'text');buildContent 里解析,缺席回落现有 COLUMNS;validate 加跨列数校验header/sample-info.ts:propertySchema 加 fields(type: 'text');buildContent 同理回落aiHint 与 validate 的文案后端(yudao-module-qcreport)
QcReportTemplatePromptBuilder.EXTRACTION_RULES 加一条规则HtmlRenderer / BASE_CSS / CanvasSafety / schema 版本号不动:docs/sql/config_export_all_*.sql(无表结构、无配置数据变更)。
defaults** ⇒ 已保存模板的 attributes 里没有这个键,buildContent 走回落分支,产物逐字不变。schemaVersion 保持 1.1:ReportTemplateSchemaCompatibilityTest 守的是「旧 JSON 读得出」,QualityComponentNode.attributes 是字符串映射,新键天然兼容。=、空标题、| 出现在标题里)各自报什么错。FrontendConformanceTest 与 CanvasSafetyTest;rowspan/hidden 的分组机制在覆盖列之后仍要工作。{{item.checkMethod}} 在真实质检单上取得到值(不起缺口告警)。mvn -o -pl yudao-module-qcreport -am test 全绿;前端 pnpm typecheck。HtmlRenderer / BASE_CSS / CanvasSafetydocs/sql/config_export_all_*.sqlheaderSpans(两级表头)是否本轮就做?不做的话「检验结果 | 结论」这层仍还原不了,MesQcReportContextMapper 里提到的那一处)还是resultText?取错会让报告结论与单据结论不一致,属于业务口径,需你确认。tool(检测工具)是否一并下发?原件没有这一列,先不下发,需要时再加。