编辑 | blame | 历史 | 原始文档

智能质检报告 — 出件接口 前端联调方案

后端模块:yudao-module-qcreport 接口前缀:/admin-api/qc-report/instance
本轮范围:**HTML 出件 + PDF 出件**。PDF 由后端调 Chromium 打印,接口只返回归档结果与签名地址;
报告实例表**不存文件地址**,PDF 统一挂在附件库(system_storage_attachment)里。

涉及页面

页面 路由 说明 状态
报告实例列表页 /qc/report-instance 分页查看已出件的报告,按模板/编号/业务单据/状态/时间筛选 已落地views/mes/qc/report/instance/index.vue
报告预览 / 详情弹窗 同上 展示渲染出的 HTML;数据快照页签;**「导出 PDF」按钮调 pdf 接口** 已落地.../instance/modules/detail.vue
模板设计器(既有) /qc/report-template 「预览」按钮需要调用本文档的 preview 接口 仍待接入(本轮只加了「AI 导入」按钮)
MES 质检单(既有,后续) 「出报告」入口,把质检单数据归一化后调 generate 后续轮次(本轮由调用方自行组织数据)

菜单已入库:「报告实例」挂在「质量管理」(parent_id=5500)下,componentmes/qc/report/instance/index,前端动态路由解析到同名 .vue

业务流程与数据带入

  1. 设计器 → 预览:设计器把当前画布保存成版本后,用「当前画布对应的模板 + 版本」加一份示例数据调用 preview
    拿到 HTML 直接展示。**预览不消耗报告编号**,可以反复调用;**预览接受草稿版本**(见「业务规则」)。
  2. 业务系统 → 出件:调用方(本轮是手工构造,后续是 MES 质检单)准备「报告上下文」→ 调 generate
    → 后端渲染、落库、冻结数据快照 → 返回实例编号与报告编号。
  3. 列表 → 详情:列表拿到 id → 调 getrenderHtml 展示;需要看「当时用的什么数据」再调 snapshot
  4. 详情 → 重新生成:调 regenerate 用**冻结的快照**重渲,覆盖产物 HTML。数据与编号都不变。
    已归档的 PDF 会被一并作废(见「业务规则」)。
  5. 详情 → 导出 PDF:点「导出 PDF」调 pdf 接口 → 后端打印并归档 → 接口返回本次的下载地址(临时签名)。
    页面要长期显示「这份报告有 PDF」请调**附件列表接口**(见「PDF 归档怎么查」),不要缓存接口返回的签名地址。

带入关系要点

  • templateId 是主线,从模板列表带入设计器、再带入出件。
  • version 可以不传:不传时后端取**模板当前的版本**(currentVersion);一定要用某个特定版本才需要显式传。
    设计器预览建议始终显式传版本——见「业务规则」里对 currentVersion 的说明。
  • reportNo 可以不传:不传时由后端按 QR + 日期 + 4 位流水 自动生成。
  • businessId / businessType 只做留痕与查询过滤,后端不校验、不解析它指向的单据。

API

方法 路径 说明 权限码
POST /qc-report/instance/preview 只渲染,**不落库、不生成、不消耗报告编号** qc-report:instance:query
POST /qc-report/instance/generate 渲染 + 落库 + 冻结数据快照 qc-report:instance:create
GET /qc-report/instance/get?id= 详情:元信息 + 渲染产物 HTML(**不含**数据快照) qc-report:instance:query
GET /qc-report/instance/snapshot?id= 取冻结的数据快照 qc-report:instance:query
GET /qc-report/instance/page 分页 qc-report:instance:query
POST /qc-report/instance/regenerate?id= 用冻结快照 + 同一模板版本重渲,覆盖产物 HTML qc-report:instance:create
POST /qc-report/instance/pdf?id= 打印实例已存的产物 HTML 成 PDF 并归档,可重复调用 qc-report:instance:export
DELETE /qc-report/instance/delete?id= 删除实例(**已归档的 PDF 一并清理**) qc-report:instance:delete

✅ 前端实例页落地时,qc-report:instance:* 的菜单与按钮权限**已同步补入 system_menu**
(菜单「报告实例」+ 查询/出件/导出PDF/删除四条按钮 + qc-report:template:ai-import 一条)。
未插这些行之前,非超管角色调这些接口会 403;超管由代码直返全部菜单,联调从不受影响。

出件 / 预览请求参数(previewgenerate 同一套)

参数 类型 必填 说明
templateId Long 模板编号
version String 模板版本号,如 v1.0。不传则取该模板的 currentVersion
reportNo String 报告编号。传了就用它(撞号会报错),不传由后端自动生成
businessId String 业务单据编号,仅留痕与查询用
businessType String 业务单据类型,如 mes_qc_oqc,仅留痕与查询用
context Object 报告数据,结构见下

context 结构(与设计器里的数据契约同构)

字段 类型 必填 说明
report Object 报告级字段。显式传 null 会被后端兜成空字段集,不会报错
report.reportNo String 报告编号。**顶层 reportNo 优先于它**
report.reportName String 报告名称
report.sampleNo String 样品编号
report.productCode String 产品编码
report.productName String 产品名称
report.spec String 规格型号
report.batchNo String 批次号
report.workOrderNo String 工单号
report.inspectType String 检验类型
report.inspector String 检验员
report.inspectDate String 检验日期
report.department String 部门
report.customerName String 客户名称
report.supplierName String 供应商名称
report.result / report.resultText / report.conclusion / report.passRate String 判定结果,由后端算出并覆盖,调用方不用传
report.total / report.passCount / report.failCount Number 同上,由后端算出
inspectionItems Array 检验项列表,**不能为空**(空数组会报错,见「错误码」)
inspectionItems[].index Number 序号,渲染 {{index}}
inspectionItems[].itemCode String 检验项编码
inspectionItems[].itemName String 检验项名称
inspectionItems[].standardValue String 标准值(文本,如 10±0.5
inspectionItems[].actualValue String 实测值
inspectionItems[].unit String 单位
inspectionItems[].upperLimit / lowerLimit Number 规格上下限。**不传就是没有规格区间**,判定会落到规则或数据自带结果上
inspectionItems[].result / resultText String 判定结果,**由后端算出并覆盖**
inspectionItems[].remark String 备注

请求示例

{
  "templateId": 5,
  "version": "v1.0",
  "businessId": "OQC20260918001",
  "businessType": "mes_qc_oqc",
  "context": {
    "report": { "reportName": "出货检验报告", "productName": "法兰", "inspector": "张三" },
    "inspectionItems": [
      { "index": 1, "itemName": "外观", "standardValue": "无划痕", "actualValue": "合格" },
      { "index": 2, "itemName": "长度", "standardValue": "10±0.5", "actualValue": 10.2, "upperLimit": 10.5, "lowerLimit": 9.5 }
    ]
  }
}

响应字段

preview 响应

字段 类型 说明
reportNo String 本次渲染用的编号。**来自入参**,没传就是空 —— 预览不生成编号
html String 完整 HTML 文档,可直接预览/打印
errors Array<String> 数据缺口清单,见下

generate 响应

字段 类型 说明
id Long 实例编号,拿它去查详情
reportNo String 最终生效的报告编号(自动生成或入参指定)
templateVersion String 本次使用的模板版本号
status Number 报告状态:0 生成中 / 1 生成成功 / 2 生成失败。当前只会返回 1
errors Array<String> 数据缺口清单

regenerate 响应

字段 类型 说明
id Long 实例编号
reportNo String 报告编号(与生成时相同)
errors Array<String> 数据缺口清单

pdf 响应

字段 类型 说明
id Long 实例编号
reportNo String 报告编号
fileName String PDF 文件名,规则是 <报告编号>.pdf(如 QR20260918-0001.pdf
byteSize Long PDF 字节数
blobId Long 文件编号(system_storage_blob 主键)
attachmentId Long 附件关联编号(system_storage_attachment 主键)
previewURL String 预览地址。**临时签名,会过期**,别存起来长期用
downloadURL String 下载地址。**临时签名,会过期**,下载动作请拿即时返回的这个值
durationMs Long 本次出件总耗时(毫秒),含排队等并发名额、必要时重启浏览器的等待
pdfDurationMs Long 打印耗时(毫秒),仅含等页面资源与打印。与 durationMs 之差就是等待成本
browserStatus String READY(复用了已在运行的浏览器)/ RESTARTED(本次重启了浏览器)。**仅供参考与排查**,前端不需要分支处理

pdf 是**显式触发**的:用户点「导出 PDF」才生成。它不会随 generate / regenerate 自动跑,
因为每导一次都要拉起 Chromium 打印(秒级),出件接口不该背这个开销。

get 响应idreportNotemplateIdtemplateVersionbusinessIdbusinessTyperenderHtmlstatuscreatorcreateTimeupdateTime
注意不含 dataSnapshot(体积大且列表/详情用不到),要它请调 snapshot

snapshot 响应{ "report": {...}, "inspectionItems": [...] },即出件当时冻结的那份数据(含算好的 PASS/FAIL 与合格率)。

page 请求参数(其余为通用分页参数 pageNo / pageSize

参数 类型 说明
templateId Long 模板编号,精确匹配
reportNo String 报告编号,**模糊匹配**
businessType String 业务单据类型,精确匹配
businessId String 业务单据编号,精确匹配
status Number 报告状态,精确匹配
createTime Array<String> 创建时间区间,["开始","结束"],格式 yyyy-MM-dd HH:mm:ss

PDF 归档怎么查(不新增专用查询接口)

报告实例表里**没有也不会有** pdfUrl / pdfPath 这类字段。PDF 统一通过 system 模块的附件库里查,
走的是全项目通用的附件接口(ERP / CRM 页面同款姿势):

方法 路径 说明
GET /system/storage-attachment/list?recordType=qc_report_instance&recordId=<实例编号> 查这份报告归档的 PDF
参数 说明
recordType 固定 qc_report_instance 报告实例的记录类型,写死这个值
recordId 报告实例编号(instance.id

响应是一个数组,每项含 url / previewURL / downloadURL / name / id(附件编号)等。
正常情况数组里最多一条(再次导出会替换掉旧的,不会越导越多)。

前端展示「PDF」按钮的逻辑建议:

  1. 进详情页时调一次附件列表接口,有数据 → 显示「下载 PDF」;没有 → 显示「导出 PDF」。
  2. 点「导出 PDF」调 pdf 接口,成功后用返回的 downloadURL 直接下载(或重新查一次附件列表刷新状态)。
  3. 不要把 downloadURL 存进前端状态长期使用:它是带签名的临时地址,过期后会 401/403。

字段展示规则

字段 展示位置 说明
reportNo 列表列、详情头部、HTML 内 列表建议加「复制编号」;**它是业务唯一键,不能重复**
templateVersion 列表列、详情 建议显示成「模板名 v1.0」;历史报告要能看出用的是哪一版
businessType / businessId 列表列、详情 建议用字典把 businessType 转成中文(如 mes_qc_oqc → 出货检验单)
status 列表列 用字典 qc_report_instance_status 渲染标签(生成中/生成成功/生成失败)
renderHtml 详情、预览 用 iframe 或整块 HTML 容器渲染;**不要把 HTML 当纯文本展示**
PDF 是否存在 详情页「下载 PDF / 导出 PDF」按钮、列表列 数据来源是附件列表接口(recordType=qc_report_instance),不是实例表。regenerate 之后要刷新这个状态
creator / createTime 列表列、详情
errors 出件/预览/重新生成后 用可折叠面板或提示条展示,不要静默丢弃,见「业务规则」

业务规则说明

场景 规则
报告编号谁生成 后端。格式 QR + yyyyMMdd + '-' + 4 位流水(如 QR20260918-0001),**按天独立,跨天从 0001 重来**。传入 reportNo 可覆盖
指定编号撞号 报错并**指明是哪个编号**(不是笼统「编号已存在」)。要么换编号,要么留空让后端生成
预览不消耗序号 preview 只渲染,不落库、不取流水号。可以反复点预览,不会把当天的编号跳号
删掉的编号不会被重用 删除报告是软删除,编号仍然占位,当天的流水**只增不减**。删掉 …-0001 之后下一份是 …-0002,**不会回到 0001**。前端不要假设「编号连续」
用哪个版本(出件) generate 要求版本已发布,草稿会被拒(1_070_101_003)。出件会把版本号冻结进实例、而实例并不存内容,用草稿出件等于给历史报告埋一颗「版本号对得上、内容已经变了」的雷
用哪个版本(预览) preview 接受草稿版本,也接受已停用版本,只要版本存在且有画布内容即可。设计器的主流程就是「保存草稿 → 点预览」,草稿会变在预览这里没有后果(不落库、不冻结数据、不消耗编号)
currentVersion 为空时 不传 version 且模板 currentVersion 为空 → 1_070_101_007。**此时 preview 也拿不到版本**,设计器预览必须显式传 version(如刚保存的草稿 v1.1
数据快照冻结 出件时把「判定后的数据」整份冻结进实例。业务系统里的数据后来怎么变,**都不影响这份历史报告**
重新生成动的是什么 只重渲**排版产物**,用的是**实例里冻结的快照**与**同一个模板版本**。数据、报告编号都不变
重新生成不要求版本仍已发布 版本后来被停用,历史报告也不能因此打不开,所以 regenerate 允许用已停用的版本
判定结果由后端算 result / resultText / conclusion / passRate / total / passCount / failCount 一律以**后端算出的**为准,调用方传了也会被覆盖
resultresultText 分工 result 是机器值(PASS / FAIL / 空字符串),resultText 是给人看的中文(合格 / 不合格 / 待判定)。**画布上显示的是 resultText**,所以产物 HTML 里搜不到 PASS 字样,别据此以为没判定
无法判定的项 既没有规格上下限、也没有命中任何规则的检验项 → result 为空、resultText待判定,**不计入 passCount/failCount 但计入 total**,所以可能出现 total=3 pass=1 fail=1(合格率按 pass/total 算)。同时它会出现在 errors 里(如「第 1 项「外观」没有规格上下限也没有判定规则,无法判定」)
数据缺口清单 模板绑定了但数据里没有的字段、以及被安全策略拦下的模板属性,都会出现在 errors 里。**它只在 preview/generate/regenerate 的响应里出现,不落库** —— 事后回看历史报告时**看不到**当时有哪些字段缺值。需要留痕请在出件时自行记录
PDF 打的是哪份 HTML 打的是**实例里已经存着的那份 HTML**,不重新渲染。所以「详情页看到的」与「打出来的」必然出自同一份字节,**不会出现页面上好好的、打出来变形**
重渲即作废已归档 PDF regenerate 之后,之前导出的 PDF 会被作废(附件列表会变成空)。原因:产物 HTML 换了,再留着旧 PDF 就是「页面是新排版、下载到的是旧文件」的静默不一致。**前端在 regenerate 成功后要把「下载 PDF」按钮恢复成「导出 PDF」**
PDF 纸面与页面预览一致 A4/A3/A5/Letter、横竖向、页边距都取自模板的纸张配置,与 HTML 里 @page 用的是同一份数字,不存在「预览 210mm、PDF 打 210.5mm」这类漂移
PDF 是"显式生成"的 只有调 pdf 接口才会有 PDF。generate / regenerate 都不会顺带生成 PDF
重复导出是安全的 再导一次会替换掉旧的归档(附件仍是 1 条),不会越导越多、也不会留孤儿文件。**可以放心让用户重复点**
出件很慢时先告诉用户 首次导出或浏览器刚崩过要重启时需要几秒,接口在 durationMs 里如实返回。前端建议点按钮后转圈直到响应,不要自己设定时器判超时
并发上限 服务端同时进行的 PDF 打印有上限(默认 2),排队超过 30 秒会返回 1_070_103_007。批量导出时不要并发猛打,串行或小并发
单份报告文件多页 长报告(检验项多)会自动分页,printBackground 已开,判定底色会打出来;表头跨页重复
PDF 功能可被运维关掉 部署环境没浏览器时会关掉(yudao.qcreport.pdf.enabled=false),此时 pdf 接口返回 1_070_103_008。前端应原样提示,不要当成系统异常
删除实例 软删除。删掉后 get 返回 null(不报错),前端要兜空。**已归档的 PDF 会一并清理**
模板停用 停用的模板**不能出新件**;已有历史报告不受影响

注意事项

  • 列表/详情不要展示 dataSnapshot 字段getpage 都不返回它,这是有意的(体积大)。
  • get 对已删除的实例返回 data:null,不是报错。所有按 id 取详情的入口都要判空。
  • HTML 渲染要注意样式隔离:产物是完整 HTML 文档(自带 @page 与样式),
    直接塞进页面会污染全局样式,建议用 iframe 承载。
  • 预览响应里的 reportNo 可能为空,这是正常的(预览不生成编号),不要拿它当出件结果。
  • inspectionItems 不能传空数组:没有检验项的「报告」没有意义,后端会直接报错。
  • businessId 不会是数字类型:虽然表里是 varchar,但调用方给的可能是工单号、批次号一类的字符串,前端别按 Long 处理。
  • errors 可能很长,尤其模板刚做好时。建议折叠展示 + 显示条数。
  • 权限:接口权限码是 qc-report:instance:query / :create / :delete / :export
    已入库到 system_menu(见「API」节下的说明)。create 是**预置**的:本轮页面没有出件入口,
    它是给后续 MesQcReportApi 轮准备的,不是遗漏。
  • 「AI 导入」按钮用独立权限码 qc-report:template:ai-import:能改模板不等于能用大模型识别文件
    (每次调用都产生费用),两者必须分开授权,不能合并到模板的编辑权限里。
  • 预览必须 sandbox=""(空属性,不是省略该属性):空值 = 完全禁用脚本、表单与同源,只让 CSS 生效。
    产物 HTML 是渲染结果,不需要也不应该让它执行任何东西。
  • 重复导出不堆文件已实测:同一实例连点 3 次,库中始终只有 1 条有效附件
    (前两条附件行与 blob 行均为 deleted=1),磁盘目录里也只留当前这一份 PDF。
  • 导出的 PDF 中文依赖服务器的字体:开发机(Windows)自带微软雅黑没问题;
    生产 Linux 服务器若没装 CJK 字体会打成方框。这属于部署前置,前端不需要处理,但**验收时要在目标环境上看一眼中文**。
  • pdf 接口的返回地址是签名的临时地址,带过期时间与使用次数限制,
    不要把它写进数据库、也不要放进列表接口缓存,需要时重新查附件列表或重新导出。
  • 附件列表里的 name 是 PDF 文件名QR20260918-0001.pdf),下载时用它命名即可。

错误码

错误码 报文 触发场景
1_070_100_000 模板不存在 templateId 找不到
1_070_100_002 该模板已停用,不能生成报告 模板 status=1
1_070_101_000 模板版本不存在 version 传了但查不到
1_070_101_003 该模板版本未发布 generateversion 指向草稿(**preview 不报这个错**)
1_070_101_007 该模板还没有已发布的版本,无法生成报告,请先在设计器中发布一个版本 不传 version 且模板 currentVersion 为空(preview 同样会报,此时须显式传 version
1_070_101_008 该模板版本没有画布内容,无法生成报告,请先在设计器中设计并保存模板内容 版本有记录但画布是空的
1_070_102_000 报告实例不存在 id 找不到(get 除外,它返回 null)
1_070_102_001 报告编号「X」已被占用,请换一个编号,或留空由系统按 QR+日期+流水 自动生成 指定了重复编号。**含已被软删除的编号**(删掉的号不回收)
1_070_102_001 自动生成报告编号时连续 5 次与已有报告冲突,请稍后重试;也可在请求里显式指定一个未被占用的报告编号 仅在同一时刻有多个出件请求抢同一个流水号时出现;正常使用不应看到
1_070_102_002 报告实例缺少数据快照,无法重新生成 快照为空的脏数据
1_070_102_003 报告实例没有渲染产物 HTML,无法生成 PDF,请先对该报告执行「重新生成」 pdf 时实例的 render_html 为空(脏数据)。**提示里已给出动作:先「重新生成」**
1_070_103_004 PDF 渲染浏览器启动失败,请检查 Chromium 是否已安装 服务器没装 Chrome / 配置的 executable-path 不对。报文里会带上当前配置的浏览器来源与实际原因
1_070_103_005 PDF 生成失败 打印过程中浏览器报错(含等页面资源超时)
1_070_103_006 PDF 已生成,但归档到附件库失败 打印成功、存档失败。**注意它不等于「PDF 没生成」**,前端的提示文案不要写成「生成失败」
1_070_103_007 当前 PDF 渲染任务已达上限,请稍后重试 并发排队超过 30 秒。报文里带当前并发上限值
1_070_103_008 PDF 出件功能未启用,请联系管理员 运维把 yudao.qcreport.pdf.enabled 关成了 false
1_070_103_000 检验项列表为空,没有可判定的内容:请至少传入一个检验项(inspectionItems)再出件 inspectionItems 为空